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 "SimpleLineLayoutFunctions.h"
28
29#include "BidiRun.h"
30#include "BidiRunList.h"
31#include "EventRegion.h"
32#include "FontCache.h"
33#include "Frame.h"
34#include "GraphicsContext.h"
35#include "HitTestLocation.h"
36#include "HitTestRequest.h"
37#include "HitTestResult.h"
38#include "InlineTextBox.h"
39#include "LineInfo.h"
40#include "PaintInfo.h"
41#include "RenderBlockFlow.h"
42#include "RenderChildIterator.h"
43#include "RenderIterator.h"
44#include "RenderStyle.h"
45#include "RenderText.h"
46#include "RenderView.h"
47#include "Settings.h"
48#include "SimpleLineLayoutFlowContents.h"
49#include "SimpleLineLayoutResolver.h"
50#include "Text.h"
51#include "TextDecorationPainter.h"
52#include "TextPaintStyle.h"
53#include "TextPainter.h"
54#include <wtf/text/TextStream.h>
55
56#if ENABLE(TREE_DEBUGGING)
57#include <stdio.h>
58#endif
59
60namespace WebCore {
61namespace SimpleLineLayout {
62
63FloatRect computeOverflow(const RenderBlockFlow& flow, const FloatRect& layoutRect)
64{
65 auto overflowRect = layoutRect;
66 auto viewportSize = flow.frame().view() ? flow.frame().view()->size() : IntSize();
67 auto strokeOverflow = std::ceil(flow.style().computedStrokeWidth(viewportSize));
68 overflowRect.inflate(strokeOverflow);
69
70 auto letterSpacing = flow.style().fontCascade().letterSpacing();
71 if (letterSpacing >= 0)
72 return overflowRect;
73 // Last letter's negative spacing shrinks layout rect. Push it to visual overflow.
74 overflowRect.expand(-letterSpacing, 0);
75 return overflowRect;
76}
77
78void paintFlow(const RenderBlockFlow& flow, const Layout& layout, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
79{
80 if (paintInfo.phase == PaintPhase::EventRegion) {
81 if (!flow.visibleToHitTesting())
82 return;
83 auto paintRect = paintInfo.rect;
84 paintRect.moveBy(-paintOffset);
85 for (auto run : layout.runResolver().rangeForRect(paintRect)) {
86 FloatRect visualOverflowRect = computeOverflow(flow, run.rect());
87 paintInfo.eventRegionContext->unite(enclosingIntRect(visualOverflowRect), flow.style());
88 }
89 return;
90 }
91
92 if (paintInfo.phase != PaintPhase::Foreground)
93 return;
94
95 auto& style = flow.style();
96 if (style.visibility() != Visibility::Visible)
97 return;
98
99 TextPainter textPainter(paintInfo.context());
100 textPainter.setFont(style.fontCascade());
101 textPainter.setStyle(computeTextPaintStyle(flow.frame(), style, paintInfo));
102
103 std::unique_ptr<ShadowData> debugShadow = nullptr;
104 if (flow.settings().simpleLineLayoutDebugBordersEnabled()) {
105 debugShadow = std::make_unique<ShadowData>(IntPoint(0, 0), 10, 20, ShadowStyle::Normal, true, Color(0, 255, 0, 200));
106 textPainter.setShadow(debugShadow.get());
107 }
108
109 Optional<TextDecorationPainter> textDecorationPainter;
110 if (!style.textDecorationsInEffect().isEmpty()) {
111 const RenderText* textRenderer = childrenOfType<RenderText>(flow).first();
112 if (textRenderer) {
113 textDecorationPainter.emplace(paintInfo.context(), style.textDecorationsInEffect(), *textRenderer, false, style.fontCascade());
114 }
115 }
116
117 LayoutRect paintRect = paintInfo.rect;
118 paintRect.moveBy(-paintOffset);
119
120 auto& resolver = layout.runResolver();
121 float deviceScaleFactor = flow.document().deviceScaleFactor();
122 for (auto run : resolver.rangeForRect(paintRect)) {
123 if (run.start() == run.end())
124 continue;
125
126 FloatRect rect = run.rect();
127 FloatRect visualOverflowRect = computeOverflow(flow, rect);
128 if (paintRect.y() > visualOverflowRect.maxY() || paintRect.maxY() < visualOverflowRect.y())
129 continue;
130
131 String textWithHyphen;
132 if (run.hasHyphen())
133 textWithHyphen = run.textWithHyphen();
134 // x position indicates the line offset from the rootbox. It's always 0 in case of simple line layout.
135 TextRun textRun { run.hasHyphen() ? textWithHyphen : run.text(), 0, run.expansion(), run.expansionBehavior() };
136 textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
137 FloatPoint textOrigin { rect.x() + paintOffset.x(), roundToDevicePixel(run.baselinePosition() + paintOffset.y(), deviceScaleFactor) };
138
139 textPainter.setGlyphDisplayListIfNeeded(run.simpleRun(), paintInfo, style.fontCascade(), paintInfo.context(), textRun);
140 textPainter.paint(textRun, rect, textOrigin);
141 if (textDecorationPainter) {
142 textDecorationPainter->setWidth(rect.width());
143 textDecorationPainter->paintTextDecoration(textRun, textOrigin, rect.location() + paintOffset);
144 }
145 }
146}
147
148bool hitTestFlow(const RenderBlockFlow& flow, const Layout& layout, const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
149{
150 if (hitTestAction != HitTestForeground)
151 return false;
152
153 if (!layout.runCount())
154 return false;
155
156 auto& style = flow.style();
157 if (style.visibility() != Visibility::Visible || style.pointerEvents() == PointerEvents::None)
158 return false;
159
160 LayoutRect rangeRect = locationInContainer.boundingBox();
161 rangeRect.moveBy(-accumulatedOffset);
162 auto resolver = lineResolver(layout.runResolver());
163 auto range = resolver.rangeForRect(rangeRect);
164 for (auto it = range.begin(), end = range.end(); it != end; ++it) {
165 auto lineRect = *it;
166 lineRect.moveBy(accumulatedOffset);
167 auto& renderer = const_cast<RenderObject&>(it.renderer());
168 if (!locationInContainer.intersects(lineRect))
169 continue;
170 renderer.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset));
171 if (result.addNodeToListBasedTestResult(renderer.node(), request, locationInContainer, lineRect) == HitTestProgress::Stop)
172 return true;
173 }
174 return false;
175}
176
177void collectFlowOverflow(RenderBlockFlow& flow, const Layout& layout)
178{
179 for (auto lineRect : lineResolver(layout.runResolver())) {
180 LayoutRect visualOverflowRect = LayoutRect(computeOverflow(flow, lineRect));
181 flow.addLayoutOverflow(LayoutRect(lineRect));
182 flow.addVisualOverflow(visualOverflowRect);
183 }
184}
185
186IntRect computeBoundingBox(const RenderObject& renderer, const Layout& layout)
187{
188 auto& resolver = layout.runResolver();
189 FloatRect boundingBoxRect;
190 for (auto run : resolver.rangeForRenderer(renderer)) {
191 FloatRect rect = run.rect();
192 if (boundingBoxRect == FloatRect())
193 boundingBoxRect = rect;
194 else
195 boundingBoxRect.uniteEvenIfEmpty(rect);
196 }
197 return enclosingIntRect(boundingBoxRect);
198}
199
200IntPoint computeFirstRunLocation(const RenderObject& renderer, const Layout& layout)
201{
202 auto& resolver = layout.runResolver();
203 auto range = resolver.rangeForRenderer(renderer);
204 auto begin = range.begin();
205 if (begin == range.end())
206 return IntPoint(0, 0);
207 return flooredIntPoint((*begin).rect().location());
208}
209
210Vector<IntRect> collectAbsoluteRects(const RenderObject& renderer, const Layout& layout, const LayoutPoint& accumulatedOffset)
211{
212 Vector<IntRect> rects;
213 auto& resolver = layout.runResolver();
214 for (auto run : resolver.rangeForRenderer(renderer)) {
215 FloatRect rect = run.rect();
216 rects.append(enclosingIntRect(FloatRect(accumulatedOffset + rect.location(), rect.size())));
217 }
218 return rects;
219}
220
221Vector<FloatQuad> collectAbsoluteQuads(const RenderObject& renderer, const Layout& layout, bool* wasFixed)
222{
223 Vector<FloatQuad> quads;
224 auto& resolver = layout.runResolver();
225 for (auto run : resolver.rangeForRenderer(renderer))
226 quads.append(renderer.localToAbsoluteQuad(FloatQuad(run.rect()), UseTransforms, wasFixed));
227 return quads;
228}
229
230unsigned textOffsetForPoint(const LayoutPoint& point, const RenderText& renderer, const Layout& layout)
231{
232 auto& flow = downcast<RenderBlockFlow>(*renderer.parent());
233 ASSERT(flow.firstChild() == flow.lastChild());
234 auto& resolver = layout.runResolver();
235 auto it = resolver.runForPoint(point);
236 if (it == resolver.end())
237 return renderer.text().length();
238 auto run = *it;
239 auto& style = flow.style();
240 TextRun textRun(run.text(), run.logicalLeft(), run.expansion(), run.expansionBehavior());
241 textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
242 return run.start() + style.fontCascade().offsetForPosition(textRun, point.x() - run.logicalLeft(), true);
243}
244
245Vector<FloatQuad> collectAbsoluteQuadsForRange(const RenderObject& renderer, unsigned start, unsigned end, const Layout& layout, bool* wasFixed)
246{
247 auto& style = downcast<RenderBlockFlow>(*renderer.parent()).style();
248 Vector<FloatQuad> quads;
249 auto& resolver = layout.runResolver();
250 for (auto run : resolver.rangeForRendererWithOffsets(renderer, start, end)) {
251 // This run is fully contained.
252 if (start <= run.start() && end >= run.end()) {
253 quads.append(renderer.localToAbsoluteQuad(FloatQuad(run.rect()), UseTransforms, wasFixed));
254 continue;
255 }
256 // Partially contained run.
257 TextRun textRun(run.text(), run.logicalLeft(), run.expansion(), run.expansionBehavior());
258 textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
259 LayoutRect runRect(run.rect());
260 // Special case empty ranges.
261 if (start == end) {
262 runRect.setWidth(0);
263 quads.append(renderer.localToAbsoluteQuad(FloatQuad(runRect), UseTransforms, wasFixed));
264 continue;
265 }
266 ASSERT(start < run.end());
267 ASSERT(end > run.start());
268 auto localStart = std::max(run.start(), start) - run.start();
269 auto localEnd = std::min(run.end(), end) - run.start();
270 style.fontCascade().adjustSelectionRectForText(textRun, runRect, localStart, localEnd);
271 quads.append(renderer.localToAbsoluteQuad(FloatQuad(runRect), UseTransforms, wasFixed));
272 }
273 return quads;
274}
275
276const RenderObject& rendererForPosition(const FlowContents& flowContents, unsigned position)
277{
278 return flowContents.segmentForPosition(position).renderer;
279}
280
281void simpleLineLayoutWillBeDeleted(const Layout& layout)
282{
283 for (unsigned i = 0; i < layout.runCount(); ++i)
284 TextPainter::removeGlyphDisplayList(layout.runAt(i));
285}
286
287bool canUseForLineBoxTree(RenderBlockFlow& flow, const Layout& layout)
288{
289 // Line breaking requires some context that SLL can't provide at the moment (see RootInlineBox::setLineBreakInfo).
290 if (layout.lineCount() > 1)
291 return false;
292
293 if (layout.isPaginated())
294 return false;
295
296 if (flow.style().preserveNewline())
297 return false;
298
299 if (!flow.firstChild())
300 return false;
301
302 for (auto& child : childrenOfType<RenderObject>(flow)) {
303 if (!is<RenderText>(child))
304 return false;
305 // Simple line layout iterator can't handle renderers with zero length properly.
306 if (!downcast<RenderText>(child).length())
307 return false;
308 }
309 return true;
310}
311
312static void initializeInlineTextBox(RenderBlockFlow& flow, InlineTextBox& inlineTextBox, const RunResolver::Run& run)
313{
314 inlineTextBox.setLogicalLeft(run.logicalLeft());
315 inlineTextBox.setLogicalTop(run.rect().y());
316 inlineTextBox.setLogicalWidth(run.logicalRight() - run.logicalLeft());
317 auto overflowRect = computeOverflow(const_cast<RenderBlockFlow&>(flow), run.rect());
318 if (overflowRect != run.rect())
319 inlineTextBox.setLogicalOverflowRect(LayoutRect(overflowRect));
320
321 inlineTextBox.setHasHyphen(run.hasHyphen());
322 inlineTextBox.setExpansionWithoutGrowing(run.expansion());
323
324 auto expansionBehavior = run.expansionBehavior();
325 inlineTextBox.setCanHaveLeadingExpansion(expansionBehavior & AllowLeadingExpansion);
326 inlineTextBox.setCanHaveTrailingExpansion(expansionBehavior & AllowTrailingExpansion);
327 if (expansionBehavior & ForceTrailingExpansion)
328 inlineTextBox.setForceTrailingExpansion();
329 if (expansionBehavior & ForceLeadingExpansion)
330 inlineTextBox.setForceLeadingExpansion();
331}
332
333void generateLineBoxTree(RenderBlockFlow& flow, const Layout& layout)
334{
335 ASSERT(!flow.lineBoxes().firstLineBox());
336 if (!layout.runCount())
337 return;
338
339 Ref<BidiContext> bidiContext = BidiContext::create(0, U_LEFT_TO_RIGHT);
340 auto& resolver = layout.runResolver();
341 unsigned lineIndex = 0;
342 while (true) {
343 auto range = resolver.rangeForLine(lineIndex++);
344 if (range.begin() == range.end())
345 break;
346
347 // Generate bidi runs out of simple line layout runs.
348 BidiRunList<BidiRun> bidiRuns;
349 for (auto it = range.begin(); it != range.end(); ++it) {
350 auto run = *it;
351 bidiRuns.appendRun(std::make_unique<BidiRun>(run.localStart(), run.localEnd(), const_cast<RenderObject&>(run.renderer()), bidiContext.ptr(), U_LEFT_TO_RIGHT));
352 }
353
354 LineInfo lineInfo;
355 lineInfo.setFirstLine(!flow.lineBoxes().firstLineBox());
356 // FIXME: This is needed for flow boxes -but we don't have them yet.
357 // lineInfo.setLastLine(lastLine);
358 lineInfo.setEmpty(!bidiRuns.runCount());
359 bidiRuns.setLogicallyLastRun(bidiRuns.lastRun());
360 auto* root = flow.constructLine(bidiRuns, lineInfo);
361 bidiRuns.clear();
362 if (!root)
363 continue;
364
365 auto& rootLineBox = *root;
366 auto it = range.begin();
367 float lineWidth = 0;
368 // Set the geometry for the inlineboxes.
369 for (auto* inlineBox = rootLineBox.firstChild(); inlineBox && it != range.end(); inlineBox = inlineBox->nextOnLine(), ++it) {
370 auto run = *it;
371 initializeInlineTextBox(flow, downcast<InlineTextBox>(*inlineBox), run);
372 lineWidth += inlineBox->logicalWidth();
373 }
374
375 // Finish setting up the rootline.
376 auto iter = range.begin();
377 auto firstRun = *iter;
378 rootLineBox.setLogicalLeft(firstRun.logicalLeft());
379 rootLineBox.setLogicalWidth(lineWidth);
380 auto lineTop = firstRun.rect().y();
381 auto lineHeight = firstRun.rect().height();
382 rootLineBox.setLogicalTop(lineTop);
383 rootLineBox.setLineTopBottomPositions(lineTop, lineTop + lineHeight, lineTop, lineTop + lineHeight);
384 }
385}
386
387#if ENABLE(TREE_DEBUGGING)
388static void printPrefix(TextStream& stream, int& printedCharacters, int depth)
389{
390 stream << "-------- --";
391 printedCharacters = 0;
392 while (++printedCharacters <= depth * 2)
393 stream << " ";
394}
395
396void outputLineLayoutForFlow(TextStream& stream, const RenderBlockFlow& flow, const Layout& layout, int depth)
397{
398 int printedCharacters = 0;
399 printPrefix(stream, printedCharacters, depth);
400
401 stream << "SimpleLineLayout (" << layout.lineCount() << " lines, " << layout.runCount() << " runs) (" << &layout << ")";
402 stream.nextLine();
403 ++depth;
404
405 for (auto run : runResolver(flow, layout)) {
406 FloatRect rect = run.rect();
407 printPrefix(stream, printedCharacters, depth);
408 if (run.start() < run.end()) {
409 stream << "line " << run.lineIndex() << " run(" << run.start() << ", " << run.end() << ") " << rect << " \"" << run.text().toStringWithoutCopying().utf8().data() << "\"";
410 } else {
411 ASSERT(run.start() == run.end());
412 stream << "line break " << run.lineIndex() << " run(" << run.start() << ", " << run.end() << ") " << rect;
413 }
414 }
415 stream.nextLine();
416}
417#endif
418
419}
420}
421