1 /*
2 * Copyright (C) 2006 Apple Computer, Inc.
3 * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
4 * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
5 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
6 * Copyright (C) 2008 Rob Buis <buis@kde.org>
7 * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
8 * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved.
9 * Copyright (C) 2012 Google Inc.
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
20 *
21 * You should have received a copy of the GNU Library General Public License
22 * along with this library; see the file COPYING.LIB. If not, write to
23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
25 */
26
27 #include "third_party/blink/renderer/core/layout/svg/layout_svg_text.h"
28
29 #include "third_party/blink/renderer/core/editing/position_with_affinity.h"
30 #include "third_party/blink/renderer/core/layout/api/line_layout_item.h"
31 #include "third_party/blink/renderer/core/layout/hit_test_request.h"
32 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
33 #include "third_party/blink/renderer/core/layout/layout_analyzer.h"
34 #include "third_party/blink/renderer/core/layout/layout_state.h"
35 #include "third_party/blink/renderer/core/layout/pointer_events_hit_rules.h"
36 #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline.h"
37 #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
38 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h"
39 #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
40 #include "third_party/blink/renderer/core/layout/svg/line/svg_root_inline_box.h"
41 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
42 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
43 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
44 #include "third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.h"
45 #include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
46 #include "third_party/blink/renderer/core/paint/svg_text_painter.h"
47 #include "third_party/blink/renderer/core/style/shadow_list.h"
48 #include "third_party/blink/renderer/core/svg/svg_text_element.h"
49 #include "third_party/blink/renderer/platform/geometry/float_quad.h"
50
51 namespace blink {
52
53 namespace {
54
FindTextRoot(const LayoutObject * start)55 const LayoutSVGText* FindTextRoot(const LayoutObject* start) {
56 DCHECK(start);
57 for (; start; start = start->Parent()) {
58 if (start->IsSVGText())
59 return ToLayoutSVGText(start);
60 }
61 return nullptr;
62 }
63
64 } // namespace
65
LayoutSVGText(SVGTextElement * node)66 LayoutSVGText::LayoutSVGText(SVGTextElement* node)
67 : LayoutSVGBlock(node),
68 needs_reordering_(false),
69 needs_positioning_values_update_(false),
70 needs_transform_update_(true),
71 needs_text_metrics_update_(false) {}
72
~LayoutSVGText()73 LayoutSVGText::~LayoutSVGText() {
74 DCHECK(descendant_text_nodes_.IsEmpty());
75 }
76
StyleDidChange(StyleDifference diff,const ComputedStyle * old_style)77 void LayoutSVGText::StyleDidChange(StyleDifference diff,
78 const ComputedStyle* old_style) {
79 LayoutSVGBlock::StyleDidChange(diff, old_style);
80 SVGResources::UpdatePaints(*GetElement(), old_style, StyleRef());
81 }
82
WillBeDestroyed()83 void LayoutSVGText::WillBeDestroyed() {
84 descendant_text_nodes_.clear();
85 SVGResources::ClearPaints(*GetElement(), Style());
86 LayoutSVGBlock::WillBeDestroyed();
87 }
88
IsChildAllowed(LayoutObject * child,const ComputedStyle &) const89 bool LayoutSVGText::IsChildAllowed(LayoutObject* child,
90 const ComputedStyle&) const {
91 return child->IsSVGInline() ||
92 (child->IsText() && SVGLayoutSupport::IsLayoutableTextNode(child));
93 }
94
LocateLayoutSVGTextAncestor(LayoutObject * start)95 LayoutSVGText* LayoutSVGText::LocateLayoutSVGTextAncestor(LayoutObject* start) {
96 return const_cast<LayoutSVGText*>(FindTextRoot(start));
97 }
98
LocateLayoutSVGTextAncestor(const LayoutObject * start)99 const LayoutSVGText* LayoutSVGText::LocateLayoutSVGTextAncestor(
100 const LayoutObject* start) {
101 return FindTextRoot(start);
102 }
103
CollectDescendantTextNodes(LayoutSVGText & text_root,Vector<LayoutSVGInlineText * > & descendant_text_nodes)104 static inline void CollectDescendantTextNodes(
105 LayoutSVGText& text_root,
106 Vector<LayoutSVGInlineText*>& descendant_text_nodes) {
107 for (LayoutObject* descendant = text_root.FirstChild(); descendant;
108 descendant = descendant->NextInPreOrder(&text_root)) {
109 if (descendant->IsSVGInlineText())
110 descendant_text_nodes.push_back(ToLayoutSVGInlineText(descendant));
111 }
112 }
113
SubtreeStructureChanged(LayoutInvalidationReasonForTracing reason)114 void LayoutSVGText::SubtreeStructureChanged(
115 LayoutInvalidationReasonForTracing reason) {
116 if (BeingDestroyed() || !EverHadLayout()) {
117 DCHECK(descendant_text_nodes_.IsEmpty());
118 return;
119 }
120 if (DocumentBeingDestroyed())
121 return;
122
123 // The positioning elements cache depends on the size of each text
124 // LayoutObject in the subtree. If this changes, clear the cache. It will be
125 // rebuilt on the next layout.
126 descendant_text_nodes_.clear();
127 SetNeedsPositioningValuesUpdate();
128 SetNeedsTextMetricsUpdate();
129 // TODO(fs): Restore the passing of |reason| here.
130 LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(*this);
131 }
132
NotifySubtreeStructureChanged(LayoutObject * object,LayoutInvalidationReasonForTracing reason)133 void LayoutSVGText::NotifySubtreeStructureChanged(
134 LayoutObject* object,
135 LayoutInvalidationReasonForTracing reason) {
136 if (LayoutSVGText* layout_text = LocateLayoutSVGTextAncestor(object))
137 layout_text->SubtreeStructureChanged(reason);
138 }
139
UpdateFontAndMetrics(LayoutSVGText & text_root)140 static inline void UpdateFontAndMetrics(LayoutSVGText& text_root) {
141 bool last_character_was_white_space = true;
142 for (LayoutObject* descendant = text_root.FirstChild(); descendant;
143 descendant = descendant->NextInPreOrder(&text_root)) {
144 if (!descendant->IsSVGInlineText())
145 continue;
146 LayoutSVGInlineText& text = ToLayoutSVGInlineText(*descendant);
147 text.UpdateScaledFont();
148 text.UpdateMetricsList(last_character_was_white_space);
149 }
150 }
151
CheckDescendantTextNodeConsistency(LayoutSVGText & text,Vector<LayoutSVGInlineText * > & expected_descendant_text_nodes)152 static inline void CheckDescendantTextNodeConsistency(
153 LayoutSVGText& text,
154 Vector<LayoutSVGInlineText*>& expected_descendant_text_nodes) {
155 #if DCHECK_IS_ON()
156 Vector<LayoutSVGInlineText*> new_descendant_text_nodes;
157 CollectDescendantTextNodes(text, new_descendant_text_nodes);
158 DCHECK(new_descendant_text_nodes == expected_descendant_text_nodes);
159 #endif
160 }
161
UpdateLayout()162 void LayoutSVGText::UpdateLayout() {
163 DCHECK(NeedsLayout());
164 // This flag is set and reset as needed only within this function.
165 DCHECK(!needs_reordering_);
166 LayoutAnalyzer::Scope analyzer(*this);
167
168 ClearOffsetMappingIfNeeded();
169
170 // When laying out initially, build the character data map and propagate
171 // resulting layout attributes to all LayoutSVGInlineText children in the
172 // subtree.
173 if (!EverHadLayout()) {
174 needs_positioning_values_update_ = true;
175 needs_text_metrics_update_ = true;
176 }
177
178 bool update_parent_boundaries = false;
179
180 // If the root layout size changed (eg. window size changes), or the screen
181 // scale factor has changed, then recompute the on-screen font size. Since
182 // the computation of layout attributes uses the text metrics, we need to
183 // update them before updating the layout attributes.
184 if (needs_text_metrics_update_) {
185 // Recompute the transform before updating font and corresponding
186 // metrics. At this point our bounding box may be incorrect, so
187 // any box relative transforms will be incorrect. Since the scaled
188 // font size only needs the scaling components to be correct, this
189 // should be fine. We update the transform again after computing
190 // the bounding box below, and after that we clear the
191 // |needs_transform_update_| flag.
192 if (needs_transform_update_) {
193 local_transform_ =
194 GetElement()->CalculateTransform(SVGElement::kIncludeMotionTransform);
195 }
196
197 UpdateFontAndMetrics(*this);
198 // Font changes may change the size of the "em" unit, so we need to
199 // update positions that might depend on the font size. This is a big
200 // hammer but we have no simple way to determine if the positions of
201 // children depend on the font size.
202 needs_positioning_values_update_ = true;
203 needs_text_metrics_update_ = false;
204 update_parent_boundaries = true;
205 }
206
207 // When the x/y/dx/dy/rotate lists change, we need to recompute the layout
208 // attributes.
209 if (needs_positioning_values_update_) {
210 descendant_text_nodes_.clear();
211 CollectDescendantTextNodes(*this, descendant_text_nodes_);
212
213 SVGTextLayoutAttributesBuilder(*this).BuildLayoutAttributes();
214
215 needs_positioning_values_update_ = false;
216 needs_reordering_ = true;
217 update_parent_boundaries = true;
218 }
219
220 CheckDescendantTextNodeConsistency(*this, descendant_text_nodes_);
221
222 // Reduced version of LayoutBlock::layoutBlock(), which only takes care of SVG
223 // text. All if branches that could cause early exit in LayoutBlocks
224 // layoutBlock() method are turned into assertions.
225 DCHECK(!IsInline());
226 DCHECK(!SimplifiedLayout());
227 DCHECK(!ScrollsOverflow());
228 DCHECK(!HasControlClip());
229 DCHECK(!PositionedObjects());
230 DCHECK(!IsAnonymousBlock());
231
232 if (!FirstChild())
233 SetChildrenInline(true);
234
235 // FIXME: We need to find a way to only layout the child boxes, if needed.
236 FloatRect old_boundaries = ObjectBoundingBox();
237 DCHECK(ChildrenInline());
238
239 RebuildFloatsFromIntruding();
240
241 LayoutUnit before_edge = BorderBefore() + PaddingBefore();
242 LayoutUnit after_edge =
243 BorderAfter() + PaddingAfter() + ScrollbarLogicalHeight();
244 SetLogicalHeight(before_edge);
245
246 LayoutState state(*this);
247 LayoutInlineChildren(true, after_edge);
248
249 needs_reordering_ = false;
250
251 FloatRect new_boundaries = ObjectBoundingBox();
252 bool bounds_changed = old_boundaries != new_boundaries;
253
254 // Update the transform after laying out. Update if the bounds
255 // changed too, since the transform could depend on the bounding
256 // box.
257 if (bounds_changed || needs_transform_update_) {
258 local_transform_ =
259 GetElement()->CalculateTransform(SVGElement::kIncludeMotionTransform);
260 needs_transform_update_ = false;
261 update_parent_boundaries = true;
262 }
263
264 ClearLayoutOverflow();
265
266 // Invalidate all resources of this client if our layout changed.
267 if (EverHadLayout() && SelfNeedsLayout())
268 SVGResourcesCache::ClientLayoutChanged(*this);
269
270 // If our bounds changed, notify the parents.
271 if (update_parent_boundaries)
272 LayoutSVGBlock::SetNeedsBoundariesUpdate();
273
274 DCHECK(!needs_reordering_);
275 DCHECK(!needs_transform_update_);
276 DCHECK(!needs_text_metrics_update_);
277 DCHECK(!needs_positioning_values_update_);
278 ClearSelfNeedsLayoutOverflowRecalc();
279 ClearNeedsLayout();
280 }
281
RecalcVisualOverflow()282 void LayoutSVGText::RecalcVisualOverflow() {
283 ClearVisualOverflow();
284 LayoutObject::RecalcVisualOverflow();
285 AddSelfVisualOverflow(LayoutRect(ObjectBoundingBox()));
286 AddVisualEffectOverflow();
287 }
288
CreateRootInlineBox()289 RootInlineBox* LayoutSVGText::CreateRootInlineBox() {
290 RootInlineBox* box = new SVGRootInlineBox(LineLayoutItem(this));
291 box->SetHasVirtualLogicalHeight();
292 return box;
293 }
294
NodeAtPoint(HitTestResult & result,const HitTestLocation & hit_test_location,const PhysicalOffset & accumulated_offset,HitTestAction hit_test_action)295 bool LayoutSVGText::NodeAtPoint(HitTestResult& result,
296 const HitTestLocation& hit_test_location,
297 const PhysicalOffset& accumulated_offset,
298 HitTestAction hit_test_action) {
299 DCHECK_EQ(accumulated_offset, PhysicalOffset());
300 // We only draw in the foreground phase, so we only hit-test then.
301 if (hit_test_action != kHitTestForeground)
302 return false;
303
304 TransformedHitTestLocation local_location(hit_test_location,
305 LocalToSVGParentTransform());
306 if (!local_location)
307 return false;
308 if (!SVGLayoutSupport::IntersectsClipPath(*this, ObjectBoundingBox(),
309 *local_location))
310 return false;
311
312 if (LayoutBlock::NodeAtPoint(result, *local_location, accumulated_offset,
313 hit_test_action))
314 return true;
315
316 // Consider the bounding box if requested.
317 if (StyleRef().PointerEvents() == EPointerEvents::kBoundingBox) {
318 if (IsObjectBoundingBoxValid() &&
319 local_location->Intersects(ObjectBoundingBox())) {
320 UpdateHitTestResult(result, PhysicalOffset::FromFloatPointRound(
321 local_location->TransformedPoint()));
322 if (result.AddNodeToListBasedTestResult(GetElement(), *local_location) ==
323 kStopHitTesting)
324 return true;
325 }
326 }
327 return false;
328 }
329
PositionForPoint(const PhysicalOffset & point_in_contents) const330 PositionWithAffinity LayoutSVGText::PositionForPoint(
331 const PhysicalOffset& point_in_contents) const {
332 RootInlineBox* root_box = FirstRootBox();
333 if (!root_box)
334 return CreatePositionWithAffinity(0);
335
336 PhysicalOffset clipped_point_in_contents(point_in_contents);
337 clipped_point_in_contents -= root_box->PhysicalLocation();
338 clipped_point_in_contents.ClampNegativeToZero();
339 clipped_point_in_contents += root_box->PhysicalLocation();
340
341 DCHECK(!root_box->NextRootBox());
342 DCHECK(ChildrenInline());
343
344 auto* closest_box =
345 To<SVGRootInlineBox>(root_box)->ClosestLeafChildForPosition(
346 clipped_point_in_contents);
347 if (!closest_box)
348 return CreatePositionWithAffinity(0);
349
350 return closest_box->GetLineLayoutItem().PositionForPoint(
351 PhysicalOffset(clipped_point_in_contents.left, closest_box->Y()));
352 }
353
AbsoluteQuads(Vector<FloatQuad> & quads,MapCoordinatesFlags mode) const354 void LayoutSVGText::AbsoluteQuads(Vector<FloatQuad>& quads,
355 MapCoordinatesFlags mode) const {
356 quads.push_back(LocalToAbsoluteQuad(StrokeBoundingBox(), mode));
357 }
358
Paint(const PaintInfo & paint_info) const359 void LayoutSVGText::Paint(const PaintInfo& paint_info) const {
360 SVGTextPainter(*this).Paint(paint_info);
361 }
362
ObjectBoundingBox() const363 FloatRect LayoutSVGText::ObjectBoundingBox() const {
364 if (const RootInlineBox* box = FirstRootBox())
365 return FloatRect(box->FrameRect());
366 return FloatRect();
367 }
368
StrokeBoundingBox() const369 FloatRect LayoutSVGText::StrokeBoundingBox() const {
370 if (!FirstRootBox())
371 return FloatRect();
372 return SVGLayoutSupport::ExtendTextBBoxWithStroke(*this, ObjectBoundingBox());
373 }
374
VisualRectInLocalSVGCoordinates() const375 FloatRect LayoutSVGText::VisualRectInLocalSVGCoordinates() const {
376 if (!FirstRootBox())
377 return FloatRect();
378 const FloatRect object_bounds = ObjectBoundingBox();
379 return SVGLayoutSupport::ComputeVisualRectForText(*this, object_bounds,
380 object_bounds);
381 }
382
AddOutlineRects(Vector<PhysicalRect> & rects,const PhysicalOffset &,NGOutlineType) const383 void LayoutSVGText::AddOutlineRects(Vector<PhysicalRect>& rects,
384 const PhysicalOffset&,
385 NGOutlineType) const {
386 rects.push_back(PhysicalRect::EnclosingRect(ObjectBoundingBox()));
387 }
388
IsObjectBoundingBoxValid() const389 bool LayoutSVGText::IsObjectBoundingBoxValid() const {
390 // If we don't have any line boxes, then consider the bbox invalid.
391 return FirstLineBox();
392 }
393
AddChild(LayoutObject * child,LayoutObject * before_child)394 void LayoutSVGText::AddChild(LayoutObject* child, LayoutObject* before_child) {
395 LayoutSVGBlock::AddChild(child, before_child);
396
397 SVGResourcesCache::ClientWasAddedToTree(*child);
398 SubtreeStructureChanged(layout_invalidation_reason::kChildChanged);
399 }
400
RemoveChild(LayoutObject * child)401 void LayoutSVGText::RemoveChild(LayoutObject* child) {
402 SVGResourcesCache::ClientWillBeRemovedFromTree(*child);
403 SubtreeStructureChanged(layout_invalidation_reason::kChildChanged);
404
405 LayoutSVGBlock::RemoveChild(child);
406 }
407
408 } // namespace blink
409