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) 2008 Rob Buis <buis@kde.org>
6 * Copyright (C) Research In Motion Limited 2010. 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 "RenderSVGInlineText.h"
26
27#include "CSSFontSelector.h"
28#include "FloatConversion.h"
29#include "FloatQuad.h"
30#include "RenderBlock.h"
31#include "RenderSVGRoot.h"
32#include "RenderSVGText.h"
33#include "SVGInlineTextBox.h"
34#include "SVGRenderingContext.h"
35#include "SVGRootInlineBox.h"
36#include "StyleFontSizeFunctions.h"
37#include "StyleResolver.h"
38#include "VisiblePosition.h"
39#include <wtf/IsoMallocInlines.h>
40
41namespace WebCore {
42
43WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGInlineText);
44
45static String applySVGWhitespaceRules(const String& string, bool preserveWhiteSpace)
46{
47 String newString = string;
48 if (preserveWhiteSpace) {
49 // Spec: When xml:space="preserve", the SVG user agent will do the following using a
50 // copy of the original character data content. It will convert all newline and tab
51 // characters into space characters. Then, it will draw all space characters, including
52 // leading, trailing and multiple contiguous space characters.
53 newString.replace('\t', ' ');
54 newString.replace('\n', ' ');
55 newString.replace('\r', ' ');
56 return newString;
57 }
58
59 // Spec: When xml:space="default", the SVG user agent will do the following using a
60 // copy of the original character data content. First, it will remove all newline
61 // characters. Then it will convert all tab characters into space characters.
62 // Then, it will strip off all leading and trailing space characters.
63 // Then, all contiguous space characters will be consolidated.
64 newString.replace('\n', emptyString());
65 newString.replace('\r', emptyString());
66 newString.replace('\t', ' ');
67 return newString;
68}
69
70RenderSVGInlineText::RenderSVGInlineText(Text& textNode, const String& string)
71 : RenderText(textNode, applySVGWhitespaceRules(string, false))
72 , m_scalingFactor(1)
73 , m_layoutAttributes(*this)
74{
75}
76
77String RenderSVGInlineText::originalText() const
78{
79 return textNode().data();
80}
81
82void RenderSVGInlineText::setRenderedText(const String& text)
83{
84 RenderText::setRenderedText(text);
85 if (auto* textAncestor = RenderSVGText::locateRenderSVGTextAncestor(*this))
86 textAncestor->subtreeTextDidChange(this);
87}
88
89void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
90{
91 RenderText::styleDidChange(diff, oldStyle);
92 updateScaledFont();
93
94 bool newPreserves = style().whiteSpace() == WhiteSpace::Pre;
95 bool oldPreserves = oldStyle ? oldStyle->whiteSpace() == WhiteSpace::Pre : false;
96 if (oldPreserves && !newPreserves) {
97 setText(applySVGWhitespaceRules(originalText(), false), true);
98 return;
99 }
100
101 if (!oldPreserves && newPreserves) {
102 setText(applySVGWhitespaceRules(originalText(), true), true);
103 return;
104 }
105
106 if (diff != StyleDifference::Layout)
107 return;
108
109 // The text metrics may be influenced by style changes.
110 if (auto* textAncestor = RenderSVGText::locateRenderSVGTextAncestor(*this))
111 textAncestor->subtreeStyleDidChange(this);
112}
113
114std::unique_ptr<InlineTextBox> RenderSVGInlineText::createTextBox()
115{
116 auto box = std::make_unique<SVGInlineTextBox>(*this);
117 box->setHasVirtualLogicalHeight();
118 return box;
119}
120
121LayoutRect RenderSVGInlineText::localCaretRect(InlineBox* box, unsigned caretOffset, LayoutUnit*)
122{
123 if (!is<InlineTextBox>(box))
124 return LayoutRect();
125
126 auto& textBox = downcast<InlineTextBox>(*box);
127 if (caretOffset < textBox.start() || caretOffset > textBox.start() + textBox.len())
128 return LayoutRect();
129
130 // Use the edge of the selection rect to determine the caret rect.
131 if (caretOffset < textBox.start() + textBox.len()) {
132 LayoutRect rect = textBox.localSelectionRect(caretOffset, caretOffset + 1);
133 LayoutUnit x = textBox.isLeftToRightDirection() ? rect.x() : rect.maxX();
134 return LayoutRect(x, rect.y(), caretWidth, rect.height());
135 }
136
137 LayoutRect rect = textBox.localSelectionRect(caretOffset - 1, caretOffset);
138 LayoutUnit x = textBox.isLeftToRightDirection() ? rect.maxX() : rect.x();
139 return LayoutRect(x, rect.y(), caretWidth, rect.height());
140}
141
142FloatRect RenderSVGInlineText::floatLinesBoundingBox() const
143{
144 FloatRect boundingBox;
145 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
146 boundingBox.unite(box->calculateBoundaries());
147 return boundingBox;
148}
149
150IntRect RenderSVGInlineText::linesBoundingBox() const
151{
152 return enclosingIntRect(floatLinesBoundingBox());
153}
154
155bool RenderSVGInlineText::characterStartsNewTextChunk(int position) const
156{
157 ASSERT(position >= 0);
158 ASSERT(position < static_cast<int>(text().length()));
159
160 // Each <textPath> element starts a new text chunk, regardless of any x/y values.
161 if (!position && parent()->isSVGTextPath() && !previousSibling())
162 return true;
163
164 const SVGCharacterDataMap::const_iterator it = m_layoutAttributes.characterDataMap().find(static_cast<unsigned>(position + 1));
165 if (it == m_layoutAttributes.characterDataMap().end())
166 return false;
167
168 return it->value.x != SVGTextLayoutAttributes::emptyValue() || it->value.y != SVGTextLayoutAttributes::emptyValue();
169}
170
171VisiblePosition RenderSVGInlineText::positionForPoint(const LayoutPoint& point, const RenderFragmentContainer*)
172{
173 if (!firstTextBox() || text().isEmpty())
174 return createVisiblePosition(0, DOWNSTREAM);
175
176 float baseline = m_scaledFont.fontMetrics().floatAscent();
177
178 RenderBlock* containingBlock = this->containingBlock();
179 ASSERT(containingBlock);
180
181 // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates.
182 FloatPoint absolutePoint(point);
183 absolutePoint.moveBy(containingBlock->location());
184
185 float closestDistance = std::numeric_limits<float>::max();
186 float closestDistancePosition = 0;
187 const SVGTextFragment* closestDistanceFragment = nullptr;
188 SVGInlineTextBox* closestDistanceBox = nullptr;
189
190 AffineTransform fragmentTransform;
191 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
192 if (!is<SVGInlineTextBox>(*box))
193 continue;
194
195 auto& textBox = downcast<SVGInlineTextBox>(*box);
196 Vector<SVGTextFragment>& fragments = textBox.textFragments();
197
198 unsigned textFragmentsSize = fragments.size();
199 for (unsigned i = 0; i < textFragmentsSize; ++i) {
200 const SVGTextFragment& fragment = fragments.at(i);
201 FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height);
202 fragment.buildFragmentTransform(fragmentTransform);
203 if (!fragmentTransform.isIdentity())
204 fragmentRect = fragmentTransform.mapRect(fragmentRect);
205
206 float distance = powf(fragmentRect.x() - absolutePoint.x(), 2) +
207 powf(fragmentRect.y() + fragmentRect.height() / 2 - absolutePoint.y(), 2);
208
209 if (distance < closestDistance) {
210 closestDistance = distance;
211 closestDistanceBox = &textBox;
212 closestDistanceFragment = &fragment;
213 closestDistancePosition = fragmentRect.x();
214 }
215 }
216 }
217
218 if (!closestDistanceFragment)
219 return createVisiblePosition(0, DOWNSTREAM);
220
221 int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true);
222 return createVisiblePosition(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
223}
224
225void RenderSVGInlineText::updateScaledFont()
226{
227 computeNewScaledFontForStyle(*this, style(), m_scalingFactor, m_scaledFont);
228}
229
230void RenderSVGInlineText::computeNewScaledFontForStyle(const RenderObject& renderer, const RenderStyle& style, float& scalingFactor, FontCascade& scaledFont)
231{
232 // Alter font-size to the right on-screen value to avoid scaling the glyphs themselves, except when GeometricPrecision is specified
233 scalingFactor = SVGRenderingContext::calculateScreenFontSizeScalingFactor(renderer);
234 if (!scalingFactor || style.fontDescription().textRenderingMode() == TextRenderingMode::GeometricPrecision) {
235 scalingFactor = 1;
236 scaledFont = style.fontCascade();
237 return;
238 }
239
240 auto fontDescription = style.fontDescription();
241
242 // FIXME: We need to better handle the case when we compute very small fonts below (below 1pt).
243 fontDescription.setComputedSize(Style::computedFontSizeFromSpecifiedSizeForSVGInlineText(fontDescription.computedSize(), fontDescription.isAbsoluteSize(), scalingFactor, renderer.document()));
244
245 // SVG controls its own glyph orientation, so don't allow writing-mode
246 // to affect it.
247 if (fontDescription.orientation() != FontOrientation::Horizontal)
248 fontDescription.setOrientation(FontOrientation::Horizontal);
249
250 scaledFont = FontCascade(WTFMove(fontDescription), 0, 0);
251 scaledFont.update(&renderer.document().fontSelector());
252}
253
254}
255