| /* |
| * Copyright (C) 2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "InlineLine.h" |
| |
| #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| |
| #include <wtf/IsoMallocInlines.h> |
| |
| namespace WebCore { |
| namespace Layout { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(Line); |
| |
| bool Line::Content::isVisuallyEmpty() const |
| { |
| // Return true for empty inline containers like <span></span>. |
| for (auto& run : m_runs) { |
| if (run->inlineItem.isContainerStart() || run->inlineItem.isContainerEnd()) |
| continue; |
| if (!run->isCollapsed) |
| return false; |
| } |
| return true; |
| } |
| |
| Line::Content::Run::Run(const InlineItem& inlineItem, const Display::Rect& logicalRect, TextContext textContext, bool isCollapsed, bool canBeExtended) |
| : inlineItem(inlineItem) |
| , logicalRect(logicalRect) |
| , textContext(textContext) |
| , isCollapsed(isCollapsed) |
| , canBeExtended(canBeExtended) |
| { |
| } |
| |
| Line::Line(const LayoutState& layoutState, LayoutUnit logicalLeft, LayoutUnit availableWidth) |
| : m_layoutState(layoutState) |
| , m_content(std::make_unique<Line::Content>()) |
| , m_logicalTopLeft(logicalLeft, 0) |
| , m_lineLogicalWidth(availableWidth) |
| , m_skipVerticalAligment(true) |
| { |
| } |
| |
| Line::Line(const LayoutState& layoutState, const LayoutPoint& topLeft, LayoutUnit availableWidth, LayoutUnit minimumHeight, LayoutUnit baselineOffset) |
| : m_layoutState(layoutState) |
| , m_content(std::make_unique<Line::Content>()) |
| , m_logicalTopLeft(topLeft) |
| , m_baseline({ baselineOffset, minimumHeight - baselineOffset, { } }) |
| , m_contentLogicalHeight(minimumHeight) |
| , m_lineLogicalWidth(availableWidth) |
| { |
| } |
| |
| std::unique_ptr<Line::Content> Line::close() |
| { |
| removeTrailingTrimmableContent(); |
| if (!m_skipVerticalAligment) { |
| // Convert inline run geometry from relative to the baseline to relative to logical top. |
| for (auto& run : m_content->runs()) { |
| auto adjustedLogicalTop = run->logicalRect.top() + baselineOffset(); |
| run->logicalRect.setTop(adjustedLogicalTop); |
| } |
| } |
| m_content->setLogicalRect({ logicalTop(), logicalLeft(), contentLogicalWidth(), logicalHeight() }); |
| m_content->setBaseline(m_baseline); |
| return WTFMove(m_content); |
| } |
| |
| void Line::removeTrailingTrimmableContent() |
| { |
| // Collapse trimmable trailing content |
| LayoutUnit trimmableWidth; |
| for (auto* trimmableRun : m_trimmableContent) { |
| trimmableRun->isCollapsed = true; |
| trimmableWidth += trimmableRun->logicalRect.width(); |
| } |
| m_contentLogicalWidth -= trimmableWidth; |
| } |
| |
| void Line::moveLogicalLeft(LayoutUnit delta) |
| { |
| if (!delta) |
| return; |
| ASSERT(delta > 0); |
| // Shrink the line and move the items. |
| m_logicalTopLeft.move(delta, 0); |
| m_lineLogicalWidth -= delta; |
| for (auto& run : m_content->runs()) |
| run->logicalRect.moveHorizontally(delta); |
| } |
| |
| void Line::moveLogicalRight(LayoutUnit delta) |
| { |
| ASSERT(delta > 0); |
| m_lineLogicalWidth -= delta; |
| } |
| |
| LayoutUnit Line::trailingTrimmableWidth() const |
| { |
| LayoutUnit trimmableWidth; |
| for (auto* trimmableRun : m_trimmableContent) |
| trimmableWidth += trimmableRun->logicalRect.width(); |
| return trimmableWidth; |
| } |
| |
| void Line::appendNonBreakableSpace(const InlineItem& inlineItem, const Display::Rect& logicalRect) |
| { |
| m_content->runs().append(std::make_unique<Content::Run>(inlineItem, logicalRect, Content::Run::TextContext { }, false, false)); |
| m_contentLogicalWidth += logicalRect.width(); |
| } |
| |
| void Line::appendInlineContainerStart(const InlineItem& inlineItem, LayoutUnit logicalWidth) |
| { |
| auto logicalRect = Display::Rect { }; |
| logicalRect.setLeft(contentLogicalRight()); |
| logicalRect.setWidth(logicalWidth); |
| |
| if (!m_skipVerticalAligment) { |
| auto logicalHeight = inlineItemHeight(inlineItem); |
| adjustBaselineAndLineHeight(inlineItem, logicalHeight); |
| |
| auto& displayBox = m_layoutState.displayBoxForLayoutBox(inlineItem.layoutBox()); |
| auto logicalTop = -inlineItem.style().fontMetrics().ascent() - displayBox.borderTop() - displayBox.paddingTop().valueOr(0); |
| logicalRect.setTop(logicalTop); |
| logicalRect.setHeight(logicalHeight); |
| } |
| appendNonBreakableSpace(inlineItem, logicalRect); |
| } |
| |
| void Line::appendInlineContainerEnd(const InlineItem& inlineItem, LayoutUnit logicalWidth) |
| { |
| // This is really just a placeholder to mark the end of the inline level container. |
| auto logicalRect = Display::Rect { 0, contentLogicalRight(), logicalWidth, 0 }; |
| appendNonBreakableSpace(inlineItem, logicalRect); |
| } |
| |
| void Line::appendTextContent(const InlineTextItem& inlineItem, LayoutUnit logicalWidth) |
| { |
| auto isTrimmable = TextUtil::isTrimmableContent(inlineItem); |
| if (!isTrimmable) |
| m_trimmableContent.clear(); |
| |
| auto shouldCollapseCompletely = [&] { |
| if (!isTrimmable) |
| return false; |
| // Leading whitespace. |
| auto& runs = m_content->runs(); |
| if (runs.isEmpty()) |
| return true; |
| // Check if the last item is trimmable as well. |
| for (int index = runs.size() - 1; index >= 0; --index) { |
| auto& inlineItem = runs[index]->inlineItem; |
| if (inlineItem.isBox()) |
| return false; |
| if (inlineItem.isText()) |
| return TextUtil::isTrimmableContent(inlineItem); |
| ASSERT(inlineItem.isContainerStart() || inlineItem.isContainerEnd()); |
| } |
| return true; |
| }; |
| |
| // Collapsed line items don't contribute to the line width. |
| auto isCompletelyCollapsed = shouldCollapseCompletely(); |
| auto canBeExtended = !isCompletelyCollapsed && !inlineItem.isCollapsed(); |
| |
| auto logicalRect = Display::Rect { }; |
| logicalRect.setLeft(contentLogicalRight()); |
| logicalRect.setWidth(logicalWidth); |
| if (!m_skipVerticalAligment) { |
| logicalRect.setTop(-inlineItem.style().fontMetrics().ascent()); |
| logicalRect.setHeight(inlineItemHeight(inlineItem)); |
| } |
| |
| auto textContext = Content::Run::TextContext { inlineItem.start(), inlineItem.isCollapsed() ? 1 : inlineItem.length() }; |
| auto lineItem = std::make_unique<Content::Run>(inlineItem, logicalRect, textContext, isCompletelyCollapsed, canBeExtended); |
| if (isTrimmable) |
| m_trimmableContent.add(lineItem.get()); |
| |
| m_content->runs().append(WTFMove(lineItem)); |
| m_contentLogicalWidth += isCompletelyCollapsed ? LayoutUnit() : logicalWidth; |
| } |
| |
| void Line::appendNonReplacedInlineBox(const InlineItem& inlineItem, LayoutUnit logicalWidth) |
| { |
| auto& displayBox = m_layoutState.displayBoxForLayoutBox(inlineItem.layoutBox()); |
| auto horizontalMargin = displayBox.horizontalMargin(); |
| auto logicalRect = Display::Rect { }; |
| |
| logicalRect.setLeft(contentLogicalRight() + horizontalMargin.start); |
| logicalRect.setWidth(logicalWidth); |
| if (!m_skipVerticalAligment) { |
| auto logicalHeight = inlineItemHeight(inlineItem); |
| adjustBaselineAndLineHeight(inlineItem, logicalHeight); |
| |
| logicalRect.setTop(-logicalHeight); |
| logicalRect.setHeight(logicalHeight); |
| } |
| |
| m_content->runs().append(std::make_unique<Content::Run>(inlineItem, logicalRect, Content::Run::TextContext { }, false, false)); |
| m_contentLogicalWidth += (logicalWidth + horizontalMargin.start + horizontalMargin.end); |
| m_trimmableContent.clear(); |
| } |
| |
| void Line::appendReplacedInlineBox(const InlineItem& inlineItem, LayoutUnit logicalWidth) |
| { |
| // FIXME Surely replaced boxes behave differently. |
| appendNonReplacedInlineBox(inlineItem, logicalWidth); |
| } |
| |
| void Line::appendHardLineBreak(const InlineItem& inlineItem) |
| { |
| auto ascent = inlineItem.layoutBox().style().fontMetrics().ascent(); |
| auto logicalRect = Display::Rect { -ascent, contentLogicalRight(), { }, logicalHeight() }; |
| m_content->runs().append(std::make_unique<Content::Run>(inlineItem, logicalRect, Content::Run::TextContext { }, false, false)); |
| } |
| |
| void Line::adjustBaselineAndLineHeight(const InlineItem& inlineItem, LayoutUnit runHeight) |
| { |
| ASSERT(!inlineItem.isContainerEnd() && !inlineItem.isText()); |
| auto& layoutBox = inlineItem.layoutBox(); |
| auto& style = layoutBox.style(); |
| |
| if (inlineItem.isContainerStart()) { |
| auto& fontMetrics = style.fontMetrics(); |
| auto halfLeading = halfLeadingMetrics(fontMetrics, style.computedLineHeight()); |
| if (halfLeading.descent > 0) |
| m_baseline.descent = std::max(m_baseline.descent, halfLeading.descent); |
| if (halfLeading.ascent > 0) |
| m_baseline.ascent = std::max(m_baseline.ascent, halfLeading.ascent); |
| m_contentLogicalHeight = std::max(m_contentLogicalHeight, baselineAlignedContentHeight()); |
| return; |
| } |
| // Replaced and non-replaced inline level box. |
| // FIXME: We need to look inside the inline-block's formatting context and check the lineboxes (if any) to be able to baseline align. |
| if (layoutBox.establishesInlineFormattingContext()) { |
| if (runHeight == logicalHeight()) |
| return; |
| // FIXME: This fails when the line height difference comes from font-size diff. |
| m_baseline.descent = std::max<LayoutUnit>(0, m_baseline.descent); |
| m_baseline.ascent = std::max(runHeight, m_baseline.ascent); |
| m_contentLogicalHeight = std::max(m_contentLogicalHeight, baselineAlignedContentHeight()); |
| return; |
| } |
| // 0 descent -> baseline aligment for now. |
| m_baseline.descent = std::max<LayoutUnit>(0, m_baseline.descent); |
| m_baseline.ascent = std::max(runHeight, m_baseline.ascent); |
| m_contentLogicalHeight = std::max(m_contentLogicalHeight, baselineAlignedContentHeight()); |
| } |
| |
| LayoutUnit Line::inlineItemHeight(const InlineItem& inlineItem) const |
| { |
| ASSERT(!m_skipVerticalAligment); |
| auto& fontMetrics = inlineItem.style().fontMetrics(); |
| if (inlineItem.isLineBreak() || is<InlineTextItem>(inlineItem)) |
| return fontMetrics.height(); |
| |
| auto& layoutBox = inlineItem.layoutBox(); |
| ASSERT(m_layoutState.hasDisplayBox(layoutBox)); |
| auto& displayBox = m_layoutState.displayBoxForLayoutBox(layoutBox); |
| |
| if (layoutBox.isFloatingPositioned()) |
| return displayBox.marginBox().height(); |
| |
| if (layoutBox.isReplaced()) |
| return displayBox.height(); |
| |
| if (inlineItem.isContainerStart() || inlineItem.isContainerEnd()) |
| return fontMetrics.height() + displayBox.verticalBorder() + displayBox.verticalPadding().valueOr(0); |
| |
| // Non-replaced inline box (e.g. inline-block) |
| return displayBox.height(); |
| } |
| |
| LineBox::Baseline Line::halfLeadingMetrics(const FontMetrics& fontMetrics, LayoutUnit lineLogicalHeight) |
| { |
| auto ascent = fontMetrics.ascent(); |
| auto descent = fontMetrics.descent(); |
| // 10.8.1 Leading and half-leading |
| auto leading = lineLogicalHeight - (ascent + descent); |
| // Inline tree is all integer based. |
| auto adjustedAscent = std::max((ascent + leading / 2).floor(), 0); |
| auto adjustedDescent = std::max((descent + leading / 2).ceil(), 0); |
| return { adjustedAscent, adjustedDescent, adjustedAscent }; |
| } |
| |
| } |
| } |
| |
| #endif |