1/*
2 * Copyright (C) 2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "InlineLineBreaker.h"
28
29#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31#include "FontCascade.h"
32#include "Hyphenation.h"
33#include "InlineRunProvider.h"
34#include "TextUtil.h"
35#include <wtf/IsoMallocInlines.h>
36
37namespace WebCore {
38namespace Layout {
39
40WTF_MAKE_ISO_ALLOCATED_IMPL(InlineLineBreaker);
41
42InlineLineBreaker::InlineLineBreaker(const LayoutState& layoutState, const InlineContent& inlineContent, const Vector<InlineRunProvider::Run>& inlineRuns)
43 : m_layoutState(layoutState)
44 , m_inlineContent(inlineContent)
45 , m_inlineRuns(inlineRuns)
46{
47}
48
49Optional<InlineLineBreaker::Run> InlineLineBreaker::nextRun(LayoutUnit contentLogicalLeft, LayoutUnit availableWidth, bool lineIsEmpty)
50{
51 if (isAtContentEnd())
52 return WTF::nullopt;
53
54 InlineRunProvider::Run currentInlineRun = m_inlineRuns[m_currentRunIndex];
55 // Adjust the current run if it is split midword.
56 if (m_splitPosition) {
57 ASSERT(currentInlineRun.isText());
58 m_splitPosition = WTF::nullopt;
59 }
60
61 if (currentInlineRun.isLineBreak()) {
62 ++m_currentRunIndex;
63 return Run { Run::Position::LineEnd, 0, currentInlineRun };
64 }
65
66 auto contentWidth = runWidth(currentInlineRun, contentLogicalLeft);
67 // 1. Plenty of space left.
68 if (contentWidth <= availableWidth) {
69 ++m_currentRunIndex;
70 return Run { lineIsEmpty ? Run::Position::LineBegin : Run::Position::Undetermined, contentWidth, currentInlineRun };
71 }
72
73 // 2. No space left whatsoever.
74 if (availableWidth <= 0) {
75 ++m_currentRunIndex;
76 return Run { Run::Position::LineBegin, contentWidth, currentInlineRun };
77 }
78
79 // 3. Some space left. Let's find out what we need to do with this run.
80 auto breakingBehavior = lineBreakingBehavior(currentInlineRun, lineIsEmpty);
81 if (breakingBehavior == LineBreakingBehavior::Keep) {
82 ++m_currentRunIndex;
83 return Run { lineIsEmpty ? Run::Position::LineBegin : Run::Position::Undetermined, contentWidth, currentInlineRun };
84 }
85
86 if (breakingBehavior == LineBreakingBehavior::WrapToNextLine) {
87 ++m_currentRunIndex;
88 return Run { Run::Position::LineBegin, contentWidth, currentInlineRun };
89 }
90
91 ASSERT(breakingBehavior == LineBreakingBehavior::Break);
92 // Split content.
93 return splitRun(currentInlineRun, contentLogicalLeft, availableWidth, lineIsEmpty);
94}
95
96bool InlineLineBreaker::isAtContentEnd() const
97{
98 return m_currentRunIndex == m_inlineRuns.size();
99}
100
101InlineLineBreaker::LineBreakingBehavior InlineLineBreaker::lineBreakingBehavior(const InlineRunProvider::Run& inlineRun, bool lineIsEmpty)
102{
103 // Line breaking behaviour:
104 // 1. Whitesapce collapse on -> push whitespace to next line.
105 // 2. Whitespace collapse off -> whitespace is split where possible.
106 // 3. Non-whitespace -> first run on the line -> either split or kept on the line. (depends on overflow-wrap)
107 // 4. Non-whitespace -> already content on the line -> either gets split (word-break: break-all) or gets pushed to the next line.
108 // (Hyphenate when possible)
109 // 5. Non-text type -> next line
110 auto& style = inlineRun.style();
111
112 if (inlineRun.isWhitespace())
113 return style.collapseWhiteSpace() ? LineBreakingBehavior::WrapToNextLine : LineBreakingBehavior::Break;
114
115 if (inlineRun.isNonWhitespace()) {
116 auto shouldHypenate = !m_hyphenationIsDisabled && style.hyphens() == Hyphens::Auto && canHyphenate(style.locale());
117 if (shouldHypenate)
118 return LineBreakingBehavior::Break;
119
120 if (style.autoWrap()) {
121 // Break any word
122 if (style.wordBreak() == WordBreak::BreakAll)
123 return LineBreakingBehavior::Break;
124
125 // Break first run on line.
126 if (lineIsEmpty && style.breakWords() && style.preserveNewline())
127 return LineBreakingBehavior::Break;
128 }
129
130 // Non-breakable non-whitespace run.
131 return lineIsEmpty ? LineBreakingBehavior::Keep : LineBreakingBehavior::WrapToNextLine;
132 }
133
134 ASSERT(inlineRun.isBox() || inlineRun.isFloat());
135 // Non-text inline runs.
136 return LineBreakingBehavior::WrapToNextLine;
137}
138
139LayoutUnit InlineLineBreaker::runWidth(const InlineRunProvider::Run& inlineRun, LayoutUnit contentLogicalLeft) const
140{
141 ASSERT(!inlineRun.isLineBreak());
142
143 if (inlineRun.isText())
144 return textWidth(inlineRun, contentLogicalLeft);
145
146 ASSERT(inlineRun.isBox() || inlineRun.isFloat());
147 auto& inlineItem = inlineRun.inlineItem();
148 auto& layoutBox = inlineItem.layoutBox();
149 ASSERT(m_layoutState.hasDisplayBox(layoutBox));
150 auto& displayBox = m_layoutState.displayBoxForLayoutBox(layoutBox);
151 return inlineItem.nonBreakableStart() + displayBox.width() + inlineItem.nonBreakableEnd();
152}
153
154LayoutUnit InlineLineBreaker::textWidth(const InlineRunProvider::Run& inlineRun, LayoutUnit contentLogicalLeft) const
155{
156 // FIXME: Find a way to merge this and InlineFormattingContext::Geometry::runWidth.
157 auto& inlineItem = inlineRun.inlineItem();
158 auto textContext = inlineRun.textContext();
159 auto startPosition = textContext->start();
160 auto length = textContext->isCollapsed() ? 1 : textContext->length();
161
162 // FIXME: It does not do proper kerning/ligature handling.
163 LayoutUnit width;
164 auto iterator = m_inlineContent.find(const_cast<InlineItem*>(&inlineItem));
165#if !ASSERT_DISABLED
166 auto inlineItemEnd = m_inlineContent.end();
167#endif
168 while (length) {
169 ASSERT(iterator != inlineItemEnd);
170 auto& currentInlineItem = **iterator;
171 auto inlineItemLength = currentInlineItem.textContent().length();
172 auto endPosition = std::min<ItemPosition>(startPosition + length, inlineItemLength);
173 auto textWidth = TextUtil::width(currentInlineItem, startPosition, endPosition, contentLogicalLeft);
174
175 auto nonBreakableStart = !startPosition ? currentInlineItem.nonBreakableStart() : 0_lu;
176 auto nonBreakableEnd = endPosition == inlineItemLength ? currentInlineItem.nonBreakableEnd() : 0_lu;
177 auto contentWidth = nonBreakableStart + textWidth + nonBreakableEnd;
178 contentLogicalLeft += contentWidth;
179 width += contentWidth;
180 length -= (endPosition - startPosition);
181
182 startPosition = 0;
183 ++iterator;
184 }
185 return width;
186}
187
188InlineLineBreaker::Run InlineLineBreaker::splitRun(const InlineRunProvider::Run& inlineRun, LayoutUnit, LayoutUnit, bool)
189{
190 return { Run::Position::Undetermined, { }, inlineRun };
191}
192
193Optional<ItemPosition> InlineLineBreaker::adjustSplitPositionWithHyphenation(const InlineRunProvider::Run&, ItemPosition, LayoutUnit, LayoutUnit, bool) const
194{
195 return { };
196}
197
198}
199}
200#endif
201