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