2 * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
3 * Copyright (C) 2006 Apple Computer Inc.
4 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
5 * Copyright (C) 2008 Rob Buis <buis@kde.org>
6 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
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.
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.
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.
26 #include "core/layout/svg/LayoutSVGInlineText.h"
28 #include "core/css/CSSFontSelector.h"
29 #include "core/css/FontSize.h"
30 #include "core/dom/StyleEngine.h"
31 #include "core/editing/TextAffinity.h"
32 #include "core/editing/VisiblePosition.h"
33 #include "core/layout/svg/LayoutSVGText.h"
34 #include "core/layout/svg/SVGLayoutSupport.h"
35 #include "core/layout/svg/line/SVGInlineTextBox.h"
39 static PassRefPtr
<StringImpl
> applySVGWhitespaceRules(PassRefPtr
<StringImpl
> string
, bool preserveWhiteSpace
)
41 if (preserveWhiteSpace
) {
42 // Spec: When xml:space="preserve", the SVG user agent will do the following using a
43 // copy of the original character data content. It will convert all newline and tab
44 // characters into space characters. Then, it will draw all space characters, including
45 // leading, trailing and multiple contiguous space characters.
46 RefPtr
<StringImpl
> newString
= string
->replace('\t', ' ');
47 newString
= newString
->replace('\n', ' ');
48 newString
= newString
->replace('\r', ' ');
49 return newString
.release();
52 // Spec: When xml:space="default", the SVG user agent will do the following using a
53 // copy of the original character data content. First, it will remove all newline
54 // characters. Then it will convert all tab characters into space characters.
55 // Then, it will strip off all leading and trailing space characters.
56 // Then, all contiguous space characters will be consolidated.
57 RefPtr
<StringImpl
> newString
= string
->replace('\n', StringImpl::empty());
58 newString
= newString
->replace('\r', StringImpl::empty());
59 newString
= newString
->replace('\t', ' ');
60 return newString
.release();
63 static float squaredDistanceToClosestPoint(const FloatRect
& rect
, const FloatPoint
& point
)
65 FloatPoint closestPoint
;
66 closestPoint
.setX(std::max(std::min(point
.x(), rect
.maxX()), rect
.x()));
67 closestPoint
.setY(std::max(std::min(point
.y(), rect
.maxY()), rect
.y()));
68 return (point
- closestPoint
).diagonalLengthSquared();
71 LayoutSVGInlineText::LayoutSVGInlineText(Node
* n
, PassRefPtr
<StringImpl
> string
)
72 : LayoutText(n
, applySVGWhitespaceRules(string
, false))
74 , m_layoutAttributes(this)
78 void LayoutSVGInlineText::setTextInternal(PassRefPtr
<StringImpl
> text
)
80 LayoutText::setTextInternal(text
);
81 if (LayoutSVGText
* textLayoutObject
= LayoutSVGText::locateLayoutSVGTextAncestor(this))
82 textLayoutObject
->subtreeTextDidChange(this);
85 void LayoutSVGInlineText::styleDidChange(StyleDifference diff
, const ComputedStyle
* oldStyle
)
87 LayoutText::styleDidChange(diff
, oldStyle
);
90 bool newPreserves
= style() ? style()->whiteSpace() == PRE
: false;
91 bool oldPreserves
= oldStyle
? oldStyle
->whiteSpace() == PRE
: false;
92 if (oldPreserves
!= newPreserves
) {
93 setText(originalText(), true);
97 if (!diff
.needsFullLayout())
100 // The text metrics may be influenced by style changes.
101 if (LayoutSVGText
* textLayoutObject
= LayoutSVGText::locateLayoutSVGTextAncestor(this))
102 textLayoutObject
->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::StyleChange
);
105 InlineTextBox
* LayoutSVGInlineText::createTextBox(int start
, unsigned short length
)
107 InlineTextBox
* box
= new SVGInlineTextBox(*this, start
, length
);
108 box
->setHasVirtualLogicalHeight();
112 LayoutRect
LayoutSVGInlineText::localCaretRect(InlineBox
* box
, int caretOffset
, LayoutUnit
*)
114 if (!box
|| !box
->isInlineTextBox())
117 InlineTextBox
* textBox
= toInlineTextBox(box
);
118 if (static_cast<unsigned>(caretOffset
) < textBox
->start() || static_cast<unsigned>(caretOffset
) > textBox
->start() + textBox
->len())
121 // Use the edge of the selection rect to determine the caret rect.
122 if (static_cast<unsigned>(caretOffset
) < textBox
->start() + textBox
->len()) {
123 LayoutRect rect
= textBox
->localSelectionRect(caretOffset
, caretOffset
+ 1);
124 LayoutUnit x
= box
->isLeftToRightDirection() ? rect
.x() : rect
.maxX();
125 return LayoutRect(x
, rect
.y(), caretWidth(), rect
.height());
128 LayoutRect rect
= textBox
->localSelectionRect(caretOffset
- 1, caretOffset
);
129 LayoutUnit x
= box
->isLeftToRightDirection() ? rect
.maxX() : rect
.x();
130 return LayoutRect(x
, rect
.y(), caretWidth(), rect
.height());
133 FloatRect
LayoutSVGInlineText::floatLinesBoundingBox() const
135 FloatRect boundingBox
;
136 for (InlineTextBox
* box
= firstTextBox(); box
; box
= box
->nextTextBox())
137 boundingBox
.unite(FloatRect(box
->calculateBoundaries()));
141 IntRect
LayoutSVGInlineText::linesBoundingBox() const
143 return enclosingIntRect(floatLinesBoundingBox());
146 bool LayoutSVGInlineText::characterStartsNewTextChunk(int position
) const
148 ASSERT(position
>= 0);
149 ASSERT(position
< static_cast<int>(textLength()));
151 // Each <textPath> element starts a new text chunk, regardless of any x/y values.
152 if (!position
&& parent()->isSVGTextPath() && !previousSibling())
155 const SVGCharacterDataMap::const_iterator it
= m_layoutAttributes
.characterDataMap().find(static_cast<unsigned>(position
+ 1));
156 if (it
== m_layoutAttributes
.characterDataMap().end())
159 return !SVGTextLayoutAttributes::isEmptyValue(it
->value
.x
) || !SVGTextLayoutAttributes::isEmptyValue(it
->value
.y
);
162 PositionWithAffinity
LayoutSVGInlineText::positionForPoint(const LayoutPoint
& point
)
164 if (!firstTextBox() || !textLength())
165 return createPositionWithAffinity(0);
167 ASSERT(m_scalingFactor
);
168 float baseline
= m_scaledFont
.fontMetrics().floatAscent() / m_scalingFactor
;
170 LayoutBlock
* containingBlock
= this->containingBlock();
171 ASSERT(containingBlock
);
173 // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates.
174 FloatPoint
absolutePoint(point
);
175 absolutePoint
.moveBy(containingBlock
->location());
177 float closestDistance
= std::numeric_limits
<float>::max();
178 float closestDistancePosition
= 0;
179 const SVGTextFragment
* closestDistanceFragment
= nullptr;
180 SVGInlineTextBox
* closestDistanceBox
= nullptr;
182 AffineTransform fragmentTransform
;
183 for (InlineTextBox
* box
= firstTextBox(); box
; box
= box
->nextTextBox()) {
184 if (!box
->isSVGInlineTextBox())
187 SVGInlineTextBox
* textBox
= toSVGInlineTextBox(box
);
188 Vector
<SVGTextFragment
>& fragments
= textBox
->textFragments();
190 unsigned textFragmentsSize
= fragments
.size();
191 for (unsigned i
= 0; i
< textFragmentsSize
; ++i
) {
192 const SVGTextFragment
& fragment
= fragments
.at(i
);
193 FloatRect
fragmentRect(fragment
.x
, fragment
.y
- baseline
, fragment
.width
, fragment
.height
);
194 fragment
.buildFragmentTransform(fragmentTransform
);
195 if (!fragmentTransform
.isIdentity())
196 fragmentRect
= fragmentTransform
.mapRect(fragmentRect
);
199 if (!fragmentRect
.contains(absolutePoint
))
200 distance
= squaredDistanceToClosestPoint(fragmentRect
, absolutePoint
);
202 if (distance
<= closestDistance
) {
203 closestDistance
= distance
;
204 closestDistanceBox
= textBox
;
205 closestDistanceFragment
= &fragment
;
206 closestDistancePosition
= fragmentRect
.x();
211 if (!closestDistanceFragment
)
212 return createPositionWithAffinity(0);
214 int offset
= closestDistanceBox
->offsetForPositionInFragment(*closestDistanceFragment
, absolutePoint
.x() - closestDistancePosition
, true);
215 return createPositionWithAffinity(offset
+ closestDistanceBox
->start(), offset
> 0 ? VP_UPSTREAM_IF_POSSIBLE
: TextAffinity::Downstream
);
218 void LayoutSVGInlineText::updateScaledFont()
220 computeNewScaledFontForStyle(this, style(), m_scalingFactor
, m_scaledFont
);
223 void LayoutSVGInlineText::computeNewScaledFontForStyle(LayoutObject
* layoutObject
, const ComputedStyle
* style
, float& scalingFactor
, Font
& scaledFont
)
226 ASSERT(layoutObject
);
228 // Alter font-size to the right on-screen value to avoid scaling the glyphs themselves, except when GeometricPrecision is specified.
229 scalingFactor
= SVGLayoutSupport::calculateScreenFontSizeScalingFactor(layoutObject
);
230 if (style
->effectiveZoom() == 1 && (scalingFactor
== 1 || !scalingFactor
)) {
232 scaledFont
= style
->font();
236 if (style
->fontDescription().textRendering() == GeometricPrecision
)
239 FontDescription
fontDescription(style
->fontDescription());
241 Document
& document
= layoutObject
->document();
242 // FIXME: We need to better handle the case when we compute very small fonts below (below 1pt).
243 fontDescription
.setComputedSize(FontSize::getComputedSizeFromSpecifiedSize(&document
, scalingFactor
, fontDescription
.isAbsoluteSize(), fontDescription
.specifiedSize(), DoNotUseSmartMinimumForFontSize
));
245 scaledFont
= Font(fontDescription
);
246 scaledFont
.update(document
.styleEngine().fontSelector());
249 LayoutRect
LayoutSVGInlineText::clippedOverflowRectForPaintInvalidation(const LayoutBoxModelObject
* paintInvalidationContainer
, const PaintInvalidationState
* paintInvalidationState
) const
251 // FIXME: The following works because LayoutSVGBlock has forced slow rect mapping of the paintInvalidationState.
252 // Should let this really work with paintInvalidationState's fast mapping and remove the assert.
253 ASSERT(!paintInvalidationState
|| !paintInvalidationState
->canMapToContainer(paintInvalidationContainer
));
254 return parent()->clippedOverflowRectForPaintInvalidation(paintInvalidationContainer
, paintInvalidationState
);
257 PassRefPtr
<StringImpl
> LayoutSVGInlineText::originalText() const
259 RefPtr
<StringImpl
> result
= LayoutText::originalText();
262 return applySVGWhitespaceRules(result
, style() && style()->whiteSpace() == PRE
);