1 /* 2 * Copyright (C) 2006 Oliver Hunt <ojh16 (at) student.canterbury.ac.nz> 3 * Copyright (C) 2006 Apple Computer Inc. 4 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann (at) kde.org> 5 * Copyright (C) 2008 Rob Buis <buis (at) 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 26 #if ENABLE(SVG) 27 #include "RenderSVGInlineText.h" 28 29 #include "CSSStyleSelector.h" 30 #include "FloatConversion.h" 31 #include "FloatQuad.h" 32 #include "RenderBlock.h" 33 #include "RenderSVGRoot.h" 34 #include "RenderSVGText.h" 35 #include "Settings.h" 36 #include "SVGImageBufferTools.h" 37 #include "SVGInlineTextBox.h" 38 #include "SVGRootInlineBox.h" 39 #include "VisiblePosition.h" 40 41 namespace WebCore { 42 43 static PassRefPtr<StringImpl> applySVGWhitespaceRules(PassRefPtr<StringImpl> string, bool preserveWhiteSpace) 44 { 45 if (preserveWhiteSpace) { 46 // Spec: When xml:space="preserve", the SVG user agent will do the following using a 47 // copy of the original character data content. It will convert all newline and tab 48 // characters into space characters. Then, it will draw all space characters, including 49 // leading, trailing and multiple contiguous space characters. 50 RefPtr<StringImpl> newString = string->replace('\t', ' '); 51 newString = newString->replace('\n', ' '); 52 newString = newString->replace('\r', ' '); 53 return newString.release(); 54 } 55 56 // Spec: When xml:space="default", the SVG user agent will do the following using a 57 // copy of the original character data content. First, it will remove all newline 58 // characters. Then it will convert all tab characters into space characters. 59 // Then, it will strip off all leading and trailing space characters. 60 // Then, all contiguous space characters will be consolidated. 61 RefPtr<StringImpl> newString = string->replace('\n', StringImpl::empty()); 62 newString = newString->replace('\r', StringImpl::empty()); 63 newString = newString->replace('\t', ' '); 64 return newString.release(); 65 } 66 67 RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> string) 68 : RenderText(n, applySVGWhitespaceRules(string, false)) 69 , m_scalingFactor(1) 70 { 71 } 72 73 void RenderSVGInlineText::destroy() 74 { 75 if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) 76 textRenderer->setNeedsPositioningValuesUpdate(); 77 78 RenderText::destroy(); 79 } 80 81 void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 82 { 83 RenderText::styleDidChange(diff, oldStyle); 84 85 if (diff == StyleDifferenceLayout) { 86 // The text metrics may be influenced by style changes. 87 if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) 88 textRenderer->setNeedsPositioningValuesUpdate(); 89 90 updateScaledFont(); 91 } 92 93 const RenderStyle* newStyle = style(); 94 if (!newStyle || newStyle->whiteSpace() != PRE) 95 return; 96 97 if (!oldStyle || oldStyle->whiteSpace() != PRE) 98 setText(applySVGWhitespaceRules(originalText(), true), true); 99 } 100 101 InlineTextBox* RenderSVGInlineText::createTextBox() 102 { 103 InlineTextBox* box = new (renderArena()) SVGInlineTextBox(this); 104 box->setHasVirtualLogicalHeight(); 105 return box; 106 } 107 108 IntRect RenderSVGInlineText::localCaretRect(InlineBox* box, int caretOffset, int*) 109 { 110 if (!box->isInlineTextBox()) 111 return IntRect(); 112 113 InlineTextBox* textBox = static_cast<InlineTextBox*>(box); 114 if (static_cast<unsigned>(caretOffset) < textBox->start() || static_cast<unsigned>(caretOffset) > textBox->start() + textBox->len()) 115 return IntRect(); 116 117 // Use the edge of the selection rect to determine the caret rect. 118 if (static_cast<unsigned>(caretOffset) < textBox->start() + textBox->len()) { 119 IntRect rect = textBox->selectionRect(0, 0, caretOffset, caretOffset + 1); 120 int x = box->isLeftToRightDirection() ? rect.x() : rect.maxX(); 121 return IntRect(x, rect.y(), caretWidth, rect.height()); 122 } 123 124 IntRect rect = textBox->selectionRect(0, 0, caretOffset - 1, caretOffset); 125 int x = box->isLeftToRightDirection() ? rect.maxX() : rect.x(); 126 return IntRect(x, rect.y(), caretWidth, rect.height()); 127 } 128 129 IntRect RenderSVGInlineText::linesBoundingBox() const 130 { 131 IntRect boundingBox; 132 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) 133 boundingBox.unite(box->calculateBoundaries()); 134 return boundingBox; 135 } 136 137 bool RenderSVGInlineText::characterStartsNewTextChunk(int position) const 138 { 139 ASSERT(m_attributes.xValues().size() == textLength()); 140 ASSERT(m_attributes.yValues().size() == textLength()); 141 ASSERT(position >= 0); 142 ASSERT(position < static_cast<int>(textLength())); 143 144 // Each <textPath> element starts a new text chunk, regardless of any x/y values. 145 if (!position && parent()->isSVGTextPath() && !previousSibling()) 146 return true; 147 148 int currentPosition = 0; 149 unsigned size = m_attributes.textMetricsValues().size(); 150 for (unsigned i = 0; i < size; ++i) { 151 const SVGTextMetrics& metrics = m_attributes.textMetricsValues().at(i); 152 153 // We found the desired character. 154 if (currentPosition == position) { 155 return m_attributes.xValues().at(position) != SVGTextLayoutAttributes::emptyValue() 156 || m_attributes.yValues().at(position) != SVGTextLayoutAttributes::emptyValue(); 157 } 158 159 currentPosition += metrics.length(); 160 if (currentPosition > position) 161 break; 162 } 163 164 // The desired position is available in the x/y list, but not in the character data values list. 165 // That means the previous character data described a single glyph, consisting of multiple unicode characters. 166 // The consequence is that the desired character does not define a new absolute x/y position, even if present in the x/y test. 167 // This code is tested by svg/W3C-SVG-1.1/text-text-06-t.svg (and described in detail, why this influences chunk detection). 168 return false; 169 } 170 171 VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point) 172 { 173 if (!firstTextBox() || !textLength()) 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.move(containingBlock->x(), containingBlock->y()); 184 185 float closestDistance = std::numeric_limits<float>::max(); 186 float closestDistancePosition = 0; 187 const SVGTextFragment* closestDistanceFragment = 0; 188 SVGInlineTextBox* closestDistanceBox = 0; 189 190 AffineTransform fragmentTransform; 191 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { 192 if (!box->isSVGInlineTextBox()) 193 continue; 194 195 SVGInlineTextBox* textBox = static_cast<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 225 void RenderSVGInlineText::updateScaledFont() 226 { 227 computeNewScaledFontForStyle(this, style(), m_scalingFactor, m_scaledFont); 228 } 229 230 void RenderSVGInlineText::computeNewScaledFontForStyle(RenderObject* renderer, const RenderStyle* style, float& scalingFactor, Font& scaledFont) 231 { 232 ASSERT(style); 233 ASSERT(renderer); 234 235 Document* document = renderer->document(); 236 ASSERT(document); 237 238 CSSStyleSelector* styleSelector = document->styleSelector(); 239 ASSERT(styleSelector); 240 241 // Alter font-size to the right on-screen value, to avoid scaling the glyphs themselves. 242 AffineTransform ctm; 243 SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(renderer, ctm); 244 scalingFactor = narrowPrecisionToFloat(sqrt((pow(ctm.xScale(), 2) + pow(ctm.yScale(), 2)) / 2)); 245 if (scalingFactor == 1 || !scalingFactor) { 246 scalingFactor = 1; 247 scaledFont = style->font(); 248 return; 249 } 250 251 FontDescription fontDescription(style->fontDescription()); 252 fontDescription.setComputedSize(fontDescription.computedSize() * scalingFactor); 253 254 scaledFont = Font(fontDescription, 0, 0); 255 scaledFont.update(styleSelector->fontSelector()); 256 } 257 258 } 259 260 #endif // ENABLE(SVG) 261