Text with simple line layout not getting pushed below float when there is not enough...
[WebKit-https.git] / Source / WebCore / rendering / SimpleLineLayout.cpp
1 /*
2  * Copyright (C) 2013 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 "SimpleLineLayout.h"
28
29 #include "FontCache.h"
30 #include "Frame.h"
31 #include "GraphicsContext.h"
32 #include "HTMLTextFormControlElement.h"
33 #include "HitTestLocation.h"
34 #include "HitTestRequest.h"
35 #include "HitTestResult.h"
36 #include "InlineTextBox.h"
37 #include "LineWidth.h"
38 #include "PaintInfo.h"
39 #include "RenderBlockFlow.h"
40 #include "RenderStyle.h"
41 #include "RenderText.h"
42 #include "RenderTextControl.h"
43 #include "RenderView.h"
44 #include "Settings.h"
45 #include "SimpleLineLayoutFunctions.h"
46 #include "Text.h"
47 #include "TextPaintStyle.h"
48 #include "break_lines.h"
49
50 namespace WebCore {
51 namespace SimpleLineLayout {
52
53 template <typename CharacterType>
54 static bool canUseForText(const CharacterType* text, unsigned length, const SimpleFontData& fontData)
55 {
56     // FIXME: <textarea maxlength=0> generates empty text node.
57     if (!length)
58         return false;
59     for (unsigned i = 0; i < length; ++i) {
60         UChar character = text[i];
61         if (character == ' ')
62             continue;
63
64         // These would be easy to support.
65         if (character == noBreakSpace)
66             return false;
67         if (character == softHyphen)
68             return false;
69
70         UCharDirection direction = u_charDirection(character);
71         if (direction == U_RIGHT_TO_LEFT || direction == U_RIGHT_TO_LEFT_ARABIC
72             || direction == U_RIGHT_TO_LEFT_EMBEDDING || direction == U_RIGHT_TO_LEFT_OVERRIDE
73             || direction == U_LEFT_TO_RIGHT_EMBEDDING || direction == U_LEFT_TO_RIGHT_OVERRIDE
74             || direction == U_POP_DIRECTIONAL_FORMAT || direction == U_BOUNDARY_NEUTRAL)
75             return false;
76
77         if (!fontData.glyphForCharacter(character))
78             return false;
79     }
80     return true;
81 }
82
83 static bool canUseForText(const RenderText& textRenderer, const SimpleFontData& fontData)
84 {
85     if (textRenderer.is8Bit())
86         return canUseForText(textRenderer.characters8(), textRenderer.textLength(), fontData);
87     return canUseForText(textRenderer.characters16(), textRenderer.textLength(), fontData);
88 }
89
90 bool canUseFor(const RenderBlockFlow& flow)
91 {
92     if (!flow.frame().settings().simpleLineLayoutEnabled())
93         return false;
94     if (!flow.firstChild())
95         return false;
96     // This currently covers <blockflow>#text</blockflow> case.
97     // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover.
98     if (flow.firstChild() != flow.lastChild())
99         return false;
100     if (!flow.firstChild()->isText())
101         return false;
102     if (!flow.isHorizontalWritingMode())
103         return false;
104     if (flow.flowThreadState() != RenderObject::NotInsideFlowThread)
105         return false;
106     if (flow.hasOutline())
107         return false;
108     if (flow.isRubyText() || flow.isRubyBase())
109         return false;
110     if (flow.parent()->isDeprecatedFlexibleBox())
111         return false;
112     // FIXME: Implementation of wrap=hard looks into lineboxes.
113     if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr))
114         return false;
115     // FIXME: Placeholders do something strange.
116     if (flow.parent()->isTextControl() && toRenderTextControl(*flow.parent()).textFormControlElement().placeholderElement())
117         return false;
118     const RenderStyle& style = flow.style();
119     if (style.textDecorationsInEffect() != TextDecorationNone)
120         return false;
121     if (style.textAlign() == JUSTIFY)
122         return false;
123     // Non-visible overflow should be pretty easy to support.
124     if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE)
125         return false;
126     if (!style.textIndent().isZero())
127         return false;
128     if (!style.wordSpacing().isZero() || style.letterSpacing())
129         return false;
130     if (style.textTransform() != TTNONE)
131         return false;
132     if (!style.isLeftToRightDirection())
133         return false;
134     if (style.lineBoxContain() != RenderStyle::initialLineBoxContain())
135         return false;
136     if (style.writingMode() != TopToBottomWritingMode)
137         return false;
138     if (style.lineBreak() != LineBreakAuto)
139         return false;
140     if (style.wordBreak() != NormalWordBreak)
141         return false;
142     if (style.unicodeBidi() != UBNormal || style.rtlOrdering() != LogicalOrder)
143         return false;
144     if (style.lineAlign() != LineAlignNone || style.lineSnap() != LineSnapNone)
145         return false;
146     if (style.hyphens() == HyphensAuto)
147         return false;
148     if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone)
149         return false;
150     if (style.textShadow())
151         return false;
152     if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style().textOverflow()))
153         return false;
154     if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER))
155         return false;
156     if (style.hasTextCombine())
157         return false;
158     if (style.backgroundClip() == TextFillBox)
159         return false;
160     if (style.borderFit() == BorderFitLines)
161         return false;
162     const RenderText& textRenderer = toRenderText(*flow.firstChild());
163     if (flow.containsFloats()) {
164         // We can't use the code path if any lines would need to be shifted below floats. This is because we don't keep per-line y coordinates.
165         float minimumWidthNeeded = textRenderer.minLogicalWidth();
166         for (auto& floatRenderer : *flow.floatingObjectSet()) {
167             ASSERT(floatRenderer);
168             float availableWidth = flow.availableLogicalWidthForLine(floatRenderer->y(), false);
169             if (availableWidth < minimumWidthNeeded)
170                 return false;
171         }
172     }
173     if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment()
174         || textRenderer.isSVGInlineText())
175         return false;
176     if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple)
177         return false;
178     if (style.font().isSVGFont())
179         return false;
180
181     // We assume that all lines have metrics based purely on the primary font.
182     auto& primaryFontData = *style.font().primaryFont();
183     if (primaryFontData.isLoading())
184         return false;
185     if (!canUseForText(textRenderer, primaryFontData))
186         return false;
187
188     return true;
189 }
190
191 struct Style {
192     Style(const RenderStyle& style)
193         : font(style.font())
194         , textAlign(style.textAlign())
195         , collapseWhitespace(style.collapseWhiteSpace())
196         , preserveNewline(style.preserveNewline())
197         , wrapLines(style.autoWrap())
198         , breakWordOnOverflow(style.overflowWrap() == BreakOverflowWrap && (wrapLines || preserveNewline))
199         , spaceWidth(font.width(TextRun(&space, 1)))
200         , tabWidth(collapseWhitespace ? 0 : style.tabSize())
201     {
202     }
203     const Font& font;
204     ETextAlign textAlign;
205     bool collapseWhitespace;
206     bool preserveNewline;
207     bool wrapLines;
208     bool breakWordOnOverflow;
209     float spaceWidth;
210     unsigned tabWidth;
211 };
212
213 static inline bool isWhitespace(UChar character, bool preserveNewline)
214 {
215     return character == ' ' || character == '\t' || (!preserveNewline && character == '\n');
216 }
217
218 template <typename CharacterType>
219 static inline unsigned skipWhitespaces(const CharacterType* text, unsigned offset, unsigned length, bool preserveNewline)
220 {
221     for (; offset < length; ++offset) {
222         if (!isWhitespace(text[offset], preserveNewline))
223             return offset;
224     }
225     return length;
226 }
227
228 template <typename CharacterType>
229 static float textWidth(const RenderText& renderText, const CharacterType* text, unsigned textLength, unsigned from, unsigned to, float xPosition, const Style& style)
230 {
231     if (style.font.isFixedPitch() || (!from && to == textLength))
232         return renderText.width(from, to - from, style.font, xPosition, nullptr, nullptr);
233
234     TextRun run(text + from, to - from);
235     run.setXPos(xPosition);
236     run.setCharactersLength(textLength - from);
237     run.setTabSize(!!style.tabWidth, style.tabWidth);
238
239     ASSERT(run.charactersLength() >= run.length());
240
241     return style.font.width(run);
242 }
243
244 template <typename CharacterType>
245 static float measureWord(unsigned start, unsigned end, float lineWidth, const Style& style, const CharacterType* text, unsigned textLength, const RenderText& textRenderer)
246 {
247     if (text[start] == ' ' && end == start + 1)
248         return style.spaceWidth;
249
250     bool measureWithEndSpace = style.collapseWhitespace && end < textLength && text[end] == ' ';
251     if (measureWithEndSpace)
252         ++end;
253     float width = textWidth(textRenderer, text, textLength, start, end, lineWidth, style);
254
255     return measureWithEndSpace ? width - style.spaceWidth : width;
256 }
257
258 template <typename CharacterType>
259 Vector<Run, 4> createLineRuns(unsigned lineStart, LineWidth& lineWidth, LazyLineBreakIterator& lineBreakIterator, const Style& style, const CharacterType* text, unsigned textLength, const RenderText& textRenderer)
260 {
261     Vector<Run, 4> lineRuns;
262     lineRuns.uncheckedAppend(Run(lineStart, 0));
263
264     unsigned wordEnd = lineStart;
265     while (wordEnd < textLength) {
266         ASSERT(!style.collapseWhitespace || !isWhitespace(text[wordEnd], style.preserveNewline));
267
268         unsigned wordStart = wordEnd;
269
270         if (style.preserveNewline && text[wordStart] == '\n') {
271             ++wordEnd;
272             // FIXME: This creates a dedicated run for newline. This is wasteful and unnecessary but it keeps test results unchanged.
273             if (wordStart > lineStart)
274                 lineRuns.append(Run(wordStart, lineRuns.last().right));
275             lineRuns.last().right = lineRuns.last().left;
276             lineRuns.last().end = wordEnd;
277             break;
278         }
279
280         if (!style.collapseWhitespace && isWhitespace(text[wordStart], style.preserveNewline))
281             wordEnd = wordStart + 1;
282         else
283             wordEnd = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, wordStart + 1);
284
285         bool wordIsPrecededByWhitespace = style.collapseWhitespace && wordStart > lineStart && isWhitespace(text[wordStart - 1], style.preserveNewline);
286         if (wordIsPrecededByWhitespace)
287             --wordStart;
288
289         float wordWidth = measureWord(wordStart, wordEnd, lineWidth.committedWidth(), style, text, textLength, textRenderer);
290
291         lineWidth.addUncommittedWidth(wordWidth);
292
293         if (style.wrapLines) {
294             // Move to the next line if the current one is full and we have something on it.
295             if (!lineWidth.fitsOnLine() && lineWidth.committedWidth())
296                 break;
297
298             // This is for white-space: pre-wrap which requires special handling for end line whitespace.
299             if (!style.collapseWhitespace && lineWidth.fitsOnLine() && wordEnd < textLength && isWhitespace(text[wordEnd], style.preserveNewline)) {
300                 // Look ahead to see if the next whitespace would fit.
301                 float whitespaceWidth = textWidth(textRenderer, text, textLength, wordEnd, wordEnd + 1, lineWidth.committedWidth(), style);
302                 if (!lineWidth.fitsOnLineIncludingExtraWidth(whitespaceWidth)) {
303                     // If not eat away the rest of the whitespace on the line.
304                     unsigned whitespaceEnd = skipWhitespaces(text, wordEnd, textLength, style.preserveNewline);
305                     // Include newline to this run too.
306                     if (whitespaceEnd < textLength && text[whitespaceEnd] == '\n')
307                         ++whitespaceEnd;
308                     lineRuns.last().end = whitespaceEnd;
309                     lineRuns.last().right = lineWidth.availableWidth();
310                     break;
311                 }
312             }
313         }
314
315         if (wordStart > lineRuns.last().end) {
316             // There were more than one consecutive whitespace.
317             ASSERT(wordIsPrecededByWhitespace);
318             // Include space to the end of the previous run.
319             lineRuns.last().end++;
320             lineRuns.last().right += style.spaceWidth;
321             // Start a new run on the same line.
322             lineRuns.append(Run(wordStart + 1, lineRuns.last().right));
323         }
324
325         if (!lineWidth.fitsOnLine() && style.breakWordOnOverflow) {
326             // Backtrack and start measuring character-by-character.
327             lineWidth.addUncommittedWidth(-lineWidth.uncommittedWidth());
328             unsigned splitEnd = wordStart;
329             for (; splitEnd < wordEnd; ++splitEnd) {
330                 float charWidth = textWidth(textRenderer, text, textLength, splitEnd, splitEnd + 1, 0, style);
331                 lineWidth.addUncommittedWidth(charWidth);
332                 if (!lineWidth.fitsOnLine() && splitEnd > lineStart)
333                     break;
334                 lineWidth.commit();
335             }
336             lineRuns.last().end = splitEnd;
337             lineRuns.last().right = lineWidth.committedWidth();
338             // To match line boxes, set single-space-only line width to zero.
339             if (text[lineRuns.last().start] == ' ' && lineRuns.last().start + 1 == lineRuns.last().end)
340                 lineRuns.last().right = lineRuns.last().left;
341             break;
342         }
343
344         lineWidth.commit();
345
346         lineRuns.last().right = lineWidth.committedWidth();
347         lineRuns.last().end = wordEnd;
348
349         if (style.collapseWhitespace)
350             wordEnd = skipWhitespaces(text, wordEnd, textLength, style.preserveNewline);
351
352         if (!lineWidth.fitsOnLine() && style.wrapLines) {
353             // The first run on the line overflows.
354             ASSERT(lineRuns.size() == 1);
355             break;
356         }
357     }
358     return lineRuns;
359 }
360
361 static float computeLineLeft(ETextAlign textAlign, const LineWidth& lineWidth)
362 {
363     float remainingWidth = lineWidth.availableWidth() - lineWidth.committedWidth();
364     float left = lineWidth.logicalLeftOffset();
365     switch (textAlign) {
366     case LEFT:
367     case WEBKIT_LEFT:
368     case TASTART:
369         return left;
370     case RIGHT:
371     case WEBKIT_RIGHT:
372     case TAEND:
373         return left + std::max<float>(remainingWidth, 0);
374     case CENTER:
375     case WEBKIT_CENTER:
376         return left + std::max<float>(remainingWidth / 2, 0);
377     case JUSTIFY:
378         break;
379     }
380     ASSERT_NOT_REACHED();
381     return 0;
382 }
383
384 static void adjustRunOffsets(Vector<Run, 4>& lineRuns, float adjustment)
385 {
386     if (!adjustment)
387         return;
388     for (unsigned i = 0; i < lineRuns.size(); ++i) {
389         lineRuns[i].left += adjustment;
390         lineRuns[i].right += adjustment;
391     }
392 }
393
394 template <typename CharacterType>
395 void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlow& flow, RenderText& textRenderer)
396 {
397     const Style style(flow.style());
398
399     const CharacterType* text = textRenderer.text()->characters<CharacterType>();
400     const unsigned textLength = textRenderer.textLength();
401
402     LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore();
403     LayoutUnit lineHeight = lineHeightFromFlow(flow);
404
405     LazyLineBreakIterator lineBreakIterator(textRenderer.text(), flow.style().locale());
406
407     unsigned lineEnd = 0;
408     while (lineEnd < textLength) {
409         if (style.collapseWhitespace)
410             lineEnd = skipWhitespaces(text, lineEnd, textLength, style.preserveNewline);
411
412         unsigned lineStart = lineEnd;
413
414         // LineWidth reads the current y position from the flow so keep it updated.
415         flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore);
416         LineWidth lineWidth(flow, false, DoNotIndentText);
417
418         auto lineRuns = createLineRuns(lineStart, lineWidth, lineBreakIterator, style, text, textLength, textRenderer);
419
420         lineEnd = lineRuns.last().end;
421         if (lineStart == lineEnd)
422             continue;
423
424         lineRuns.last().isEndOfLine = true;
425
426         float lineLeft = computeLineLeft(style.textAlign, lineWidth);
427         adjustRunOffsets(lineRuns, lineLeft);
428
429         for (unsigned i = 0; i < lineRuns.size(); ++i)
430             runs.append(lineRuns[i]);
431
432         ++lineCount;
433     }
434 }
435
436 std::unique_ptr<Layout> create(RenderBlockFlow& flow)
437 {
438     Layout::RunVector runs;
439     unsigned lineCount = 0;
440
441     RenderText& textRenderer = toRenderText(*flow.firstChild());
442     ASSERT(!textRenderer.firstTextBox());
443
444     if (textRenderer.is8Bit())
445         createTextRuns<LChar>(runs, lineCount, flow, textRenderer);
446     else
447         createTextRuns<UChar>(runs, lineCount, flow, textRenderer);
448
449     textRenderer.clearNeedsLayout();
450
451     return Layout::create(runs, lineCount);
452 }
453
454 std::unique_ptr<Layout> Layout::create(const RunVector& runVector, unsigned lineCount)
455 {
456     void* slot = WTF::fastMalloc(sizeof(Layout) + sizeof(Run) * runVector.size());
457     return std::unique_ptr<Layout>(new (NotNull, slot) Layout(runVector, lineCount));
458 }
459
460 Layout::Layout(const RunVector& runVector, unsigned lineCount)
461     : m_lineCount(lineCount)
462     , m_runCount(runVector.size())
463 {
464     memcpy(m_runs, runVector.data(), m_runCount * sizeof(Run));
465 }
466
467 }
468 }