Changeset 286784 in webkit

Timestamp:
Dec 9, 2021, 9:26:58 AM (3 years ago)
Author:
Alan Bujtas
Message:

[LFC][IFC] Refactor bidi inline box boundary handling
https://bugs.webkit.org/show_bug.cgi?id=233880

Reviewed by Antti Koivisto.

This patch remove some code complexity by introducing more suitable data types to handle
content ranges for inline boxes.

  1. The final output of the InlineDisplayContentBuilder is a list of display boxes. It's a simple "one box right after the other" type of setup with continuous geometry (ie next box starts where the previous ends +-horizontal margins) unless the content is embedded in an inline box (e.g. <span>embedded content</span>) Inline box type of display boxes enclose all their descendant content. Normally when the logical order == visual order (unidirectional content), the descendant content ends right before the "inline box end" mark (</span>). It makes it easy to compute the right edge/enclosing width for the inline box type of display boxes.
  2. However the visual order of the bidi content makes the "let's use the inline box end mark" logic unusable as we may see the "inline box end" mark even before we come across any descendant content (there are other types of confusions with random ordering).
  3. Working with a list when the content comes in a nested flavor may introduce some complexity (e.g. need to keep content ranges around).

This patch introduces a very simple DisplayBoxNode type which keeps track of the display boxes in a tree fashion.
The display boxes are still stored in a list, while this newly introduced DisplayBoxNode tree helps traversing
the nested content.
It helps finding out the insertion point at unexpected bidi boundaries (e.g. when nested content from other inline boxes
show up in a logically unrelated inline box) and it is also very valuable when computing visual geometries for those enclosing display boxes.

  • layout/formattingContexts/inline/InlineDisplayContentBuilder.cpp:

(WebCore::Layout::DisplayBoxNode::DisplayBoxNode):
(WebCore::Layout::DisplayBoxNode::appendChild):
(WebCore::Layout::AncestorStack::unwind):
(WebCore::Layout::AncestorStack::push):
(WebCore::Layout::InlineDisplayContentBuilder::ensureDisplayBoxForContainer):
(WebCore::Layout::InlineDisplayContentBuilder::adjustVisualGeometryForChildNode):
(WebCore::Layout::InlineDisplayContentBuilder::insertInlineBoxDisplayBoxForBidiBoundary): Deleted.
(WebCore::Layout::InlineDisplayContentBuilder::adjustInlineBoxDisplayBoxForBidiBoundary): Deleted.
(WebCore::Layout::InlineDisplayContentBuilder::processBidiContent): Deleted.
(WebCore::Layout::InlineDisplayContentBuilder::processOverflownRunsForEllipsis): Deleted.
(WebCore::Layout::InlineDisplayContentBuilder::collectInkOverflowForInlineBoxes): Deleted.
(WebCore::Layout::InlineDisplayContentBuilder::computeIsFirstIsLastBoxForInlineContent): Deleted.

  • layout/formattingContexts/inline/InlineDisplayContentBuilder.h:
  • layout/formattingContexts/inline/display/InlineDisplayBox.h:

(WebCore::InlineDisplay::Box::setLogicalLeft):
(WebCore::InlineDisplay::Box::setLogicalRight):
(WebCore::InlineDisplay::Box::setLogicalRect):
(WebCore::InlineDisplay::Box::setHasContent):

Location:
trunk/Source/WebCore
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r286782 r286784  
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
    1492021-12-09  Alan Bujtas  <zalan@apple.com>
    250
  • trunk/Source/WebCore/layout/formattingContexts/inline/InlineDisplayContentBuilder.cpp

    r286782 r286784  
    267267}
    268268
    269 void InlineDisplayContentBuilder::insertInlineBoxDisplayBoxForBidiBoundary(const InlineLevelBox& inlineBox, const InlineRect& inlineBoxRect, bool isFirstInlineBoxFragment, size_t insertionPoint, DisplayBoxes& boxes)
    270 {
    271     ASSERT(inlineBox.isInlineBox());
    272 
    273     auto isFirstLastBox = OptionSet<InlineDisplay::Box::PositionWithinInlineLevelBox> { };
    274     if (inlineBox.isFirstBox() && isFirstInlineBoxFragment)
    275         isFirstLastBox.add({ InlineDisplay::Box::PositionWithinInlineLevelBox::First });
    276     if (inlineBox.isLastBox())
    277         isFirstLastBox.add({ InlineDisplay::Box::PositionWithinInlineLevelBox::Last });
    278 
    279     // FIXME: Compute ink overflow.
    280     boxes.insert(insertionPoint, { m_lineIndex
    281         , InlineDisplay::Box::Type::NonRootInlineBox
    282         , inlineBox.layoutBox()
    283         , UBIDI_DEFAULT_LTR
    284         , inlineBoxRect
    285         , inlineBoxRect
    286         , { }
    287         , { }
    288         , true
    289         , isFirstLastBox });
    290 }
    291 
    292 void InlineDisplayContentBuilder::adjustInlineBoxDisplayBoxForBidiBoundary(InlineDisplay::Box& displayBox, const InlineRect& inlineBoxRect)
    293 {
    294     UNUSED_PARAM(displayBox);
    295     UNUSED_PARAM(inlineBoxRect);
    296 }
    297 
    298269void InlineDisplayContentBuilder::processNonBidiContent(const LineBuilder::LineContent& lineContent, const LineBox& lineBox, const InlineLayoutPoint& lineBoxLogicalTopLeft, DisplayBoxes& boxes)
    299270{
     
    350321}
    351322
     323
     324
     325
     326
     327
     328
     329
     330
     331
     332
     333
     334
     335
     336
     337
     338
     339
     340
     341
     342
     343
     344
     345
     346
     347
     348
     349
     350
     351
     352
     353
     354
     355
     356
     357
     358
     359
     360
     361
     362
     363
     364
     365
     366
     367
     368
     369
     370
     371
     372
     373
     374
     375
     376
     377
     378
     379
     380
     381
     382
     383
     384
     385
     386
     387
     388
     389
     390
     391
     392
     393
     394
     395
     396
     397
     398
     399
     400
     401
     402
     403
     404
     405
     406
     407
     408
     409
     410
     411
     412
     413
     414
     415
     416
     417
     418
     419
     420
     421
     422
     423
     424
     425
     426
     427
     428
     429
     430
    352431void InlineDisplayContentBuilder::processBidiContent(const LineBuilder::LineContent& lineContent, const LineBox& lineBox, const InlineLayoutPoint& lineBoxLogicalTopLeft, DisplayBoxes& boxes)
    353432{
    354433    ASSERT(lineContent.visualOrderList.size() == lineContent.runs.size());
    355434
    356     Vector<Range<size_t>> inlineBoxRangeList;
    357     auto needsNonRootInlineBoxDisplayBox = false;
    358     auto createDisplayBoxesInVisualOrderForContentRuns = [&] {
     435    AncestorStack ancestorStack;
     436    DisplayBoxNode rootDisplayBoxNode = { };
     437    ancestorStack.push(rootDisplayBoxNode, root());
     438
     439    auto contentStartInVisualOrder = InlineLayoutUnit { };
     440    auto createDisplayBoxesInVisualOrder = [&] {
    359441        auto rootInlineBoxRect = lineBox.logicalRectForRootInlineBox();
    360         auto contentRightInVisualOrder = InlineLayoutUnit { };
    361442        // First visual run's initial content position depends on the block's inline direction.
    362443        if (!root().style().isLeftToRightDirection()) {
    363444            // FIXME: This needs the block end position instead of the lineLogicalWidth.
    364             contentRightInVisualOrder += lineContent.lineLogicalWidth - rootInlineBoxRect.width();
     445            contenttInVisualOrder += lineContent.lineLogicalWidth - rootInlineBoxRect.width();
    365446        }
    366447        // Adjust the content start position with the (text)alignment offset (root inline box has the alignment offset and not the individual runs).
    367         contentRightInVisualOrder += rootInlineBoxRect.left();
    368 
     448        contentStartInVisualOrder += rootInlineBoxRect.left();
     449
     450        auto contentRightInVisualOrder = contentStartInVisualOrder;
    369451        auto& runs = lineContent.runs;
    370452        for (size_t i = 0; i < runs.size(); ++i) {
    371453            auto visualIndex = lineContent.visualOrderList[i];
    372454            auto& lineRun = runs[visualIndex];
    373 
    374             auto isContentRun = !lineRun.isInlineBoxStart() && !lineRun.isLineSpanningInlineBoxStart() && !lineRun.isInlineBoxEnd();
     455            auto& layoutBox = lineRun.layoutBox();
     456
     457            auto isContentRun = !lineRun.isInlineBoxStart() && !lineRun.isLineSpanningInlineBoxStart() && !lineRun.isInlineBoxEnd() && !lineRun.isWordBreakOpportunity();
    375458            if (!isContentRun) {
    376                 needsNonRootInlineBoxDisplayBox = true;
     459               
    377460                continue;
    378461            }
     
    384467            };
    385468
     469
    386470            if (lineRun.isText()) {
    387471                auto visualRect = visualRectRelativeToRoot(lineBox.logicalRectForTextRun(lineRun));
    388472                appendTextDisplayBox(lineRun, visualRect, boxes);
    389473                contentRightInVisualOrder += visualRect.width();
    390                 continue;
    391             }
    392             if (lineRun.isSoftLineBreak()) {
     474            } else if (lineRun.isSoftLineBreak()) {
    393475                ASSERT(!visualRectRelativeToRoot(lineBox.logicalRectForTextRun(lineRun)).width());
    394476                appendSoftLineBreakDisplayBox(lineRun, visualRectRelativeToRoot(lineBox.logicalRectForTextRun(lineRun)), boxes);
    395                 continue;
    396             }
    397             if (lineRun.isHardLineBreak()) {
    398                 ASSERT(!visualRectRelativeToRoot(lineBox.logicalRectForLineBreakBox(lineRun.layoutBox())).width());
    399                 appendHardLineBreakDisplayBox(lineRun, visualRectRelativeToRoot(lineBox.logicalRectForLineBreakBox(lineRun.layoutBox())), boxes);
    400                 continue;
    401             }
    402             if (lineRun.isBox()) {
    403                 auto& layoutBox = lineRun.layoutBox();
     477            } else if (lineRun.isHardLineBreak()) {
     478                ASSERT(!visualRectRelativeToRoot(lineBox.logicalRectForLineBreakBox(layoutBox)).width());
     479                appendHardLineBreakDisplayBox(lineRun, visualRectRelativeToRoot(lineBox.logicalRectForLineBreakBox(layoutBox)), boxes);
     480            } else if (lineRun.isBox()) {
    404481                auto& boxGeometry = formattingState().boxGeometry(layoutBox);
    405482                auto visualRect = visualRectRelativeToRoot(lineBox.logicalBorderBoxForAtomicInlineLevelBox(layoutBox, boxGeometry));
     
    407484                appendAtomicInlineLevelDisplayBox(lineRun, visualRect, boxes);
    408485                contentRightInVisualOrder += boxGeometry.marginStart() + visualRect.width() + boxGeometry.marginEnd();
    409                 continue;
    410486            }
    411             ASSERT(lineRun.isWordBreakOpportunity());
    412         }
    413     };
    414     createDisplayBoxesInVisualOrderForContentRuns();
    415 
    416     auto needsDisplayBoxHorizontalAdjustment = false;
    417     auto createDisplayBoxesInVisualOrderForInlineBoxes = [&] {
    418         // Visual order could introduce gaps and/or inject runs outside from the current inline box content.
    419         // In such cases, we need to "close" and "open" display boxes for these inline box fragments
    420         // to accommodate the current content.
    421         // We do it by finding the lowest common ancestor of the last and the current content display boxes and
    422         // traverse both ancestor chains and close/open the parent (inline) boxes.
    423         // (open here means to create a new display box, while close means to simply pop it out of parentBoxStack).
    424         // <div>a<span id=first>b&#8238;g</span>f<span id=second>e&#8237;c</span>d</div>
    425         // produces the following output (note the #8238; #8237; RTL/LTR control characters):
    426         // abcdefg
    427         // with the following, fragmented inline boxes:
    428         // a[first open]b[first close][second open]c[second close]d[second open]e[second close]f[first open]g[first close]
    429         HashMap<const Box*, size_t> inlineBoxDisplayBoxMap;
    430         ListHashSet<const Box*> parentBoxStack;
    431         parentBoxStack.add(&root());
    432 
    433         ASSERT(boxes[0].isRootInlineBox());
    434         for (size_t index = 1; index < boxes.size(); ++index) {
    435             auto& parentBox = boxes[index].layoutBox().parent();
    436             ASSERT(parentBox.isInlineBox() || &parentBox == &root());
    437 
    438             auto runParentIsCurrentInlineBox = &parentBox == parentBoxStack.last();
    439             if (runParentIsCurrentInlineBox) {
    440                 // We've got the correct inline box as parent. Nothing to do here.
    441                 continue;
     487            parentDisplayBoxNode.appendChild(boxes.size() - 1);
     488        }
     489    };
     490    createDisplayBoxesInVisualOrder();
     491
     492    if (!rootDisplayBoxNode.children.isEmpty()) {
     493        auto computeIsFirstIsLastBox = [&] {
     494            HashMap<const Box*, size_t> lastDisplayBoxIndexes;
     495            ASSERT(boxes[0].isRootInlineBox());
     496            for (size_t index = 1; index < boxes.size(); ++index) {
     497                auto& displayBox = boxes[index];
     498                if (!displayBox.isNonRootInlineBox())
     499                    continue;
     500                auto& layoutBox = displayBox.layoutBox();
     501                auto isFirstBoxOnCurrentLine = lastDisplayBoxIndexes.set(&layoutBox, index).isNewEntry;
     502                if (lineBox.inlineLevelBoxForLayoutBox(layoutBox).isFirstBox() && isFirstBoxOnCurrentLine)
     503                    displayBox.setIsFirstForLayoutBox(true);
    442504            }
    443             auto parentBoxStackEnd = parentBoxStack.end();
    444             Vector<const Box*> inlineBoxNeedingDisplayBoxList;
    445             for (auto* ancestor = &parentBox; ancestor; ancestor = &ancestor->parent()) {
    446                 ASSERT(ancestor == &root() || ancestor->isInlineBox());
    447                 auto parentBoxIterator = parentBoxStack.find(ancestor);
    448                 if (parentBoxIterator != parentBoxStackEnd) {
    449                     // This is the lowest common ancestor.
    450                     // Let's traverse both ancestor chains and create/close display boxes as needed.
    451                     Vector<const Box*> inlineBoxFragmentsToClose;
    452                     for (auto it = ++parentBoxIterator; it != parentBoxStackEnd; ++it)
    453                         inlineBoxFragmentsToClose.append(*it);
    454 
    455                     for (auto* inlineBox : makeReversedRange(inlineBoxFragmentsToClose)) {
    456                         ASSERT(inlineBox->isInlineBox());
    457                         ASSERT(inlineBoxDisplayBoxMap.contains(inlineBox));
    458                         inlineBoxRangeList.append({ inlineBoxDisplayBoxMap.get(inlineBox), index });
    459                         parentBoxStack.remove(inlineBox);
    460                     }
    461 
    462                     // Insert new display boxes for inline box fragments on bidi boundary.
    463                     for (auto* inlineBox : makeReversedRange(inlineBoxNeedingDisplayBoxList)) {
    464                         ASSERT(inlineBox->isInlineBox());
    465                         parentBoxStack.add(inlineBox);
    466 
    467                         auto createAndInsertDisplayBoxForInlineBoxFragment = [&] {
    468                             // Make sure that the "previous" display box for this particular inline box is not tracked as the "last box".
    469                             auto lastDisplayBoxForInlineBoxIndex = inlineBoxDisplayBoxMap.take(inlineBox);
    470                             auto isFirstFragment = !lastDisplayBoxForInlineBoxIndex;
    471                             if (!isFirstFragment)
    472                                 boxes[lastDisplayBoxForInlineBoxIndex].setIsLastForLayoutBox(false);
    473                             inlineBoxDisplayBoxMap.set(inlineBox, index);
    474 
    475                             auto& boxGeometry = formattingState().boxGeometry(*inlineBox);
    476                             auto visualRect = lineBox.logicalBorderBoxForInlineBox(*inlineBox, boxGeometry);
    477                             // Use the current content left as the starting point for this display box.
    478                             visualRect.setLeft(boxes[index].logicalLeft());
    479                             visualRect.moveVertically(lineBoxLogicalTopLeft.y());
    480                             // Visual width is not yet known.
    481                             visualRect.setWidth({ });
    482                             insertInlineBoxDisplayBoxForBidiBoundary(lineBox.inlineLevelBoxForLayoutBox(*inlineBox), visualRect, isFirstFragment, index, boxes);
    483                             ++index;
    484                             // Need to push the rest of the content when this inline box has margin/border/padding.
    485                             needsDisplayBoxHorizontalAdjustment = needsDisplayBoxHorizontalAdjustment
    486                                 || boxGeometry.horizontalBorder()
    487                                 || boxGeometry.horizontalPadding().value_or(0)
    488                                 || boxGeometry.marginStart()
    489                                 || boxGeometry.marginEnd();
    490                         };
    491                         createAndInsertDisplayBoxForInlineBoxFragment();
    492                     }
    493                     break;
    494                 }
    495                 // root may not be the lowest but always a common ancestor.
    496                 ASSERT(ancestor != &root());
    497                 inlineBoxNeedingDisplayBoxList.append(ancestor);
     505            for (auto index : lastDisplayBoxIndexes.values()) {
     506                if (lineBox.inlineLevelBoxForLayoutBox(boxes[index].layoutBox()).isLastBox())
     507                    boxes[index].setIsLastForLayoutBox(true);
    498508            }
    499         }
    500         // "Close" the remaining inline boxes on the stack (excluding the root).
    501         while (parentBoxStack.size() > 1) {
    502             auto* parentInlineBox = parentBoxStack.takeLast();
    503             ASSERT(inlineBoxDisplayBoxMap.contains(parentInlineBox));
    504             inlineBoxRangeList.append({ inlineBoxDisplayBoxMap.get(parentInlineBox), boxes.size() });
    505         }
    506     };
    507     if (needsNonRootInlineBoxDisplayBox)
    508         createDisplayBoxesInVisualOrderForInlineBoxes();
    509 
    510     auto adjustVisualGeometryWithInlineBoxes = [&] {
    511         size_t currentInlineBox = 0;
    512         auto accumulatedOffset = InlineLayoutUnit { };
    513 
    514         ASSERT(boxes[0].isRootInlineBox());
    515         for (size_t index = 1; index < boxes.size(); ++index) {
    516             auto& displayBox = boxes[index];
    517             displayBox.moveHorizontally(accumulatedOffset);
    518 
    519             while (currentInlineBox < inlineBoxRangeList.size() && index == inlineBoxRangeList[currentInlineBox].end() - 1) {
    520                 // We are at the end of the inline box content.
    521                 // Let's compute the inline box width and offset the rest of the content with padding/border/margin end.
    522                 auto inlineBoxRange = inlineBoxRangeList[currentInlineBox++];
    523                 auto& inlineBoxDisplayBox = boxes[inlineBoxRange.begin()];
    524                 ASSERT(inlineBoxDisplayBox.isNonRootInlineBox());
    525 
    526                 auto& boxGeometry = formattingState().boxGeometry(inlineBoxDisplayBox.layoutBox());
    527                 auto contentRight = displayBox.logicalRight();
    528                 if (inlineBoxDisplayBox.isLastForLayoutBox()) {
    529                     accumulatedOffset += boxGeometry.borderAndPaddingEnd() + boxGeometry.marginEnd();
    530                     inlineBoxDisplayBox.setLogicalRight(contentRight + boxGeometry.borderAndPaddingEnd());
    531                 } else
    532                     inlineBoxDisplayBox.setLogicalRight(contentRight);
    533             }
    534             if (displayBox.isNonRootInlineBox() && displayBox.isFirstForLayoutBox()) {
    535                 auto& layoutBox = displayBox.layoutBox();
    536                 auto& boxGeometry = formattingState().boxGeometry(layoutBox);
    537 
    538                 displayBox.moveHorizontally(boxGeometry.marginStart());
    539                 accumulatedOffset += boxGeometry.marginStart() + boxGeometry.borderAndPaddingStart();
    540             }
    541         }
    542     };
    543     if (needsDisplayBoxHorizontalAdjustment)
     509        };
     510        computeIsFirstIsLastBox();
     511
     512        auto adjustVisualGeometryWithInlineBoxes = [&] {
     513            auto contentRightInVisualOrder = lineBoxLogicalTopLeft.x() + contentStartInVisualOrder;
     514            for (auto& childDisplayBoxNode : rootDisplayBoxNode.children)
     515                adjustVisualGeometryForChildNode(*childDisplayBoxNode, contentRightInVisualOrder, lineBoxLogicalTopLeft.y(), boxes, lineBox);
     516        };
    544517        adjustVisualGeometryWithInlineBoxes();
    545 
    546     auto computeInlineBoxGeometry = [&] {
    547         ASSERT(!inlineBoxRangeList.isEmpty());
    548         for (auto& inlineBoxRange : inlineBoxRangeList) {
    549             auto& inlineBoxDisplayBox = boxes[inlineBoxRange.begin()];
    550             setInlineBoxGeometry(inlineBoxDisplayBox.layoutBox(), inlineBoxDisplayBox.logicalRect(), inlineBoxDisplayBox.isFirstForLayoutBox());
    551         }
    552     };
    553     if (needsNonRootInlineBoxDisplayBox)
    554         computeInlineBoxGeometry();
     518    }
    555519}
    556520
  • trunk/Source/WebCore/layout/formattingContexts/inline/InlineDisplayContentBuilder.h

    r286782 r286784  
    3434namespace Layout {
    3535
     36
    3637class ContainerBox;
     38
    3739class InlineFormattingState;
    3840class LineBox;
     
    5860    void appendInlineBoxDisplayBox(const Line::Run&, const InlineLevelBox&, const InlineRect&, bool linehasContent, DisplayBoxes&);
    5961    void appendSpanningInlineBoxDisplayBox(const Line::Run&, const InlineLevelBox&, const InlineRect&, DisplayBoxes&);
    60     void insertInlineBoxDisplayBoxForBidiBoundary(const InlineLevelBox&, const InlineRect&, bool isFirstInlineBoxFragment, size_t insertionPoint, DisplayBoxes&);
    61     void adjustInlineBoxDisplayBoxForBidiBoundary(InlineDisplay::Box&, const InlineRect&);
    6262
    6363    void setInlineBoxGeometry(const Box&, const InlineRect&, bool isFirstInlineBoxFragment);
     64
     65
    6466
    6567    const ContainerBox& root() const { return m_formattingContextRoot; }
  • trunk/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayBox.h

    r286541 r286784  
    119119    void adjustInkOverflow(const Layout::InlineRect& childBorderBox) { return m_inkOverflow.expandToContain(childBorderBox); }
    120120    void truncate(Layout::InlineLayoutUnit truncatedwidth = 0.f);
    121     void setLogicalRight(Layout::InlineLayoutUnit right) { m_logicalRect.setRight(right); }
     121    void setLogicalLeft(Layout::InlineLayoutUnit left)
     122    {
     123        auto offset = left - logicalLeft();
     124        m_logicalRect.setLeft(left);
     125        m_inkOverflow.setLeft(m_inkOverflow.left() + offset);
     126    }
     127    void setLogicalRight(Layout::InlineLayoutUnit right)
     128    {
     129        auto offset = right - logicalRight();
     130        m_logicalRect.setRight(right);
     131        m_inkOverflow.setRight(m_inkOverflow.right() + offset);
     132    }
     133    void setLogicalRect(const Layout::InlineRect& rect, const Layout::InlineRect& inkOverflow)
     134    {
     135        m_logicalRect = rect;
     136        m_inkOverflow = inkOverflow;
     137    }
     138    void setHasContent() { m_hasContent = true; }
    122139
    123140    std::optional<Text>& text() { return m_text; }
Note: See TracChangeset for help on using the changeset viewer.