1/*
2 * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
3 * Copyright (C) 2006 Apple Inc.
4 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
5 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
6 * Copyright (C) 2011 Torch Mobile (Beijing) CO. Ltd. All rights reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24#include "config.h"
25#include "SVGRootInlineBox.h"
26
27#include "GraphicsContext.h"
28#include "RenderSVGText.h"
29#include "RenderSVGTextPath.h"
30#include "SVGInlineFlowBox.h"
31#include "SVGInlineTextBox.h"
32#include "SVGNames.h"
33#include "SVGRenderingContext.h"
34#include "SVGTextPositioningElement.h"
35#include <wtf/IsoMallocInlines.h>
36
37namespace WebCore {
38
39WTF_MAKE_ISO_ALLOCATED_IMPL(SVGRootInlineBox);
40
41SVGRootInlineBox::SVGRootInlineBox(RenderSVGText& renderSVGText)
42 : RootInlineBox(renderSVGText)
43 , m_logicalHeight(0)
44{
45}
46
47RenderSVGText& SVGRootInlineBox::renderSVGText()
48{
49 return downcast<RenderSVGText>(blockFlow());
50}
51
52void SVGRootInlineBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit)
53{
54 ASSERT(paintInfo.phase == PaintPhase::Foreground || paintInfo.phase == PaintPhase::Selection);
55 ASSERT(!paintInfo.context().paintingDisabled());
56
57 bool isPrinting = renderSVGText().document().printing();
58 bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone;
59 bool shouldPaintSelectionHighlight = !(paintInfo.paintBehavior.contains(PaintBehavior::SkipSelectionHighlight));
60
61 PaintInfo childPaintInfo(paintInfo);
62 if (hasSelection && shouldPaintSelectionHighlight) {
63 for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
64 if (is<SVGInlineTextBox>(*child))
65 downcast<SVGInlineTextBox>(*child).paintSelectionBackground(childPaintInfo);
66 else if (is<SVGInlineFlowBox>(*child))
67 downcast<SVGInlineFlowBox>(*child).paintSelectionBackground(childPaintInfo);
68 }
69 }
70
71 SVGRenderingContext renderingContext(renderSVGText(), paintInfo, SVGRenderingContext::SaveGraphicsContext);
72 if (renderingContext.isRenderingPrepared()) {
73 for (InlineBox* child = firstChild(); child; child = child->nextOnLine())
74 child->paint(paintInfo, paintOffset, 0, 0);
75 }
76}
77
78void SVGRootInlineBox::computePerCharacterLayoutInformation()
79{
80 auto& textRoot = downcast<RenderSVGText>(blockFlow());
81
82 Vector<SVGTextLayoutAttributes*>& layoutAttributes = textRoot.layoutAttributes();
83 if (layoutAttributes.isEmpty())
84 return;
85
86 if (textRoot.needsReordering())
87 reorderValueLists(layoutAttributes);
88
89 // Perform SVG text layout phase two (see SVGTextLayoutEngine for details).
90 SVGTextLayoutEngine characterLayout(layoutAttributes);
91 layoutCharactersInTextBoxes(this, characterLayout);
92
93 // Perform SVG text layout phase three (see SVGTextChunkBuilder for details).
94 characterLayout.finishLayout();
95
96 // Perform SVG text layout phase four
97 // Position & resize all SVGInlineText/FlowBoxes in the inline box tree, resize the root box as well as the RenderSVGText parent block.
98 FloatRect childRect;
99 layoutChildBoxes(this, &childRect);
100 layoutRootBox(childRect);
101}
102
103void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGTextLayoutEngine& characterLayout)
104{
105 for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) {
106 if (is<SVGInlineTextBox>(*child)) {
107 ASSERT(is<RenderSVGInlineText>(child->renderer()));
108 characterLayout.layoutInlineTextBox(downcast<SVGInlineTextBox>(*child));
109 } else {
110 // Skip generated content.
111 Node* node = child->renderer().node();
112 if (!node)
113 continue;
114
115 auto& flowBox = downcast<SVGInlineFlowBox>(*child);
116 bool isTextPath = node->hasTagName(SVGNames::textPathTag);
117 if (isTextPath) {
118 // Build text chunks for all <textPath> children, using the line layout algorithm.
119 // This is needeed as text-anchor is just an additional startOffset for text paths.
120 SVGTextLayoutEngine lineLayout(characterLayout.layoutAttributes());
121 layoutCharactersInTextBoxes(&flowBox, lineLayout);
122
123 characterLayout.beginTextPathLayout(downcast<RenderSVGTextPath>(child->renderer()), lineLayout);
124 }
125
126 layoutCharactersInTextBoxes(&flowBox, characterLayout);
127
128 if (isTextPath)
129 characterLayout.endTextPathLayout();
130 }
131 }
132}
133
134void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start, FloatRect* childRect)
135{
136 for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) {
137 FloatRect boxRect;
138 if (is<SVGInlineTextBox>(*child)) {
139 ASSERT(is<RenderSVGInlineText>(child->renderer()));
140
141 auto& textBox = downcast<SVGInlineTextBox>(*child);
142 boxRect = textBox.calculateBoundaries();
143 textBox.setX(boxRect.x());
144 textBox.setY(boxRect.y());
145 textBox.setLogicalWidth(boxRect.width());
146 textBox.setLogicalHeight(boxRect.height());
147 } else {
148 // Skip generated content.
149 if (!child->renderer().node())
150 continue;
151
152 auto& flowBox = downcast<SVGInlineFlowBox>(*child);
153 layoutChildBoxes(&flowBox);
154
155 boxRect = flowBox.calculateBoundaries();
156 flowBox.setX(boxRect.x());
157 flowBox.setY(boxRect.y());
158 flowBox.setLogicalWidth(boxRect.width());
159 flowBox.setLogicalHeight(boxRect.height());
160 }
161 if (childRect)
162 childRect->unite(boxRect);
163 }
164}
165
166void SVGRootInlineBox::layoutRootBox(const FloatRect& childRect)
167{
168 RenderSVGText& parentBlock = renderSVGText();
169
170 // Finally, assign the root block position, now that all content is laid out.
171 LayoutRect boundingRect = enclosingLayoutRect(childRect);
172 parentBlock.setLocation(boundingRect.location());
173 parentBlock.setSize(boundingRect.size());
174
175 // Position all children relative to the parent block.
176 for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
177 // Skip generated content.
178 if (!child->renderer().node())
179 continue;
180 child->adjustPosition(-childRect.x(), -childRect.y());
181 }
182
183 // Position ourselves.
184 setX(0);
185 setY(0);
186 setLogicalWidth(childRect.width());
187 setLogicalHeight(childRect.height());
188 setLineTopBottomPositions(0, boundingRect.height(), 0, boundingRect.height());
189}
190
191InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const LayoutPoint& point)
192{
193 InlineBox* firstLeaf = firstLeafChild();
194 InlineBox* lastLeaf = lastLeafChild();
195 if (firstLeaf == lastLeaf)
196 return firstLeaf;
197
198 // FIXME: Check for vertical text!
199 InlineBox* closestLeaf = nullptr;
200 for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) {
201 if (!leaf->isSVGInlineTextBox())
202 continue;
203 if (point.y() < leaf->y())
204 continue;
205 if (point.y() > leaf->y() + leaf->virtualLogicalHeight())
206 continue;
207
208 closestLeaf = leaf;
209 if (point.x() < leaf->left() + leaf->logicalWidth())
210 return leaf;
211 }
212
213 return closestLeaf ? closestLeaf : lastLeaf;
214}
215
216bool SVGRootInlineBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit lineTop, LayoutUnit lineBottom, HitTestAction hitTestAction)
217{
218 for (InlineBox* leaf = firstLeafChild(); leaf; leaf = leaf->nextLeafChild()) {
219 if (!leaf->isSVGInlineTextBox())
220 continue;
221 if (leaf->nodeAtPoint(request, result, locationInContainer, accumulatedOffset, lineTop, lineBottom, hitTestAction))
222 return true;
223 }
224
225 return false;
226}
227
228static inline void swapItemsInLayoutAttributes(SVGTextLayoutAttributes* firstAttributes, SVGTextLayoutAttributes* lastAttributes, unsigned firstPosition, unsigned lastPosition)
229{
230 SVGCharacterDataMap::iterator itFirst = firstAttributes->characterDataMap().find(firstPosition + 1);
231 SVGCharacterDataMap::iterator itLast = lastAttributes->characterDataMap().find(lastPosition + 1);
232 bool firstPresent = itFirst != firstAttributes->characterDataMap().end();
233 bool lastPresent = itLast != lastAttributes->characterDataMap().end();
234 if (!firstPresent && !lastPresent)
235 return;
236
237 if (firstPresent && lastPresent) {
238 std::swap(itFirst->value, itLast->value);
239 return;
240 }
241
242 if (firstPresent && !lastPresent) {
243 lastAttributes->characterDataMap().set(lastPosition + 1, itFirst->value);
244 return;
245 }
246
247 // !firstPresent && lastPresent
248 firstAttributes->characterDataMap().set(firstPosition + 1, itLast->value);
249}
250
251static inline void findFirstAndLastAttributesInVector(Vector<SVGTextLayoutAttributes*>& attributes, RenderSVGInlineText* firstContext, RenderSVGInlineText* lastContext,
252 SVGTextLayoutAttributes*& first, SVGTextLayoutAttributes*& last)
253{
254 first = nullptr;
255 last = nullptr;
256
257 unsigned attributesSize = attributes.size();
258 for (unsigned i = 0; i < attributesSize; ++i) {
259 SVGTextLayoutAttributes* current = attributes[i];
260 if (!first && firstContext == &current->context())
261 first = current;
262 if (!last && lastContext == &current->context())
263 last = current;
264 if (first && last)
265 break;
266 }
267
268 ASSERT(first);
269 ASSERT(last);
270}
271
272static inline void reverseInlineBoxRangeAndValueListsIfNeeded(void* userData, Vector<InlineBox*>::iterator first, Vector<InlineBox*>::iterator last)
273{
274 ASSERT(userData);
275 Vector<SVGTextLayoutAttributes*>& attributes = *reinterpret_cast<Vector<SVGTextLayoutAttributes*>*>(userData);
276
277 // This is a copy of std::reverse(first, last). It additionally assures that the metrics map within the renderers belonging to the InlineBoxes are reordered as well.
278 while (true) {
279 if (first == last || first == --last)
280 return;
281
282 if (!is<SVGInlineTextBox>(**last) || !is<SVGInlineTextBox>(**first)) {
283 InlineBox* temp = *first;
284 *first = *last;
285 *last = temp;
286 ++first;
287 continue;
288 }
289
290 auto& firstTextBox = downcast<SVGInlineTextBox>(**first);
291 auto& lastTextBox = downcast<SVGInlineTextBox>(**last);
292
293 // Reordering is only necessary for BiDi text that is _absolutely_ positioned.
294 if (firstTextBox.len() == 1 && firstTextBox.len() == lastTextBox.len()) {
295 RenderSVGInlineText& firstContext = firstTextBox.renderer();
296 RenderSVGInlineText& lastContext = lastTextBox.renderer();
297
298 SVGTextLayoutAttributes* firstAttributes = nullptr;
299 SVGTextLayoutAttributes* lastAttributes = nullptr;
300 findFirstAndLastAttributesInVector(attributes, &firstContext, &lastContext, firstAttributes, lastAttributes);
301 swapItemsInLayoutAttributes(firstAttributes, lastAttributes, firstTextBox.start(), lastTextBox.start());
302 }
303
304 InlineBox* temp = *first;
305 *first = *last;
306 *last = temp;
307
308 ++first;
309 }
310}
311
312void SVGRootInlineBox::reorderValueLists(Vector<SVGTextLayoutAttributes*>& attributes)
313{
314 Vector<InlineBox*> leafBoxesInLogicalOrder;
315 collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder, reverseInlineBoxRangeAndValueListsIfNeeded, &attributes);
316}
317
318} // namespace WebCore
319