1/* 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. 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 35namespace WebCore { 36 37static 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 61RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> string) 62 : RenderText(n, applySVGWhitespaceRules(string, false)) 63 , m_scalingFactor(1) 64 , m_layoutAttributes(this) 65{ 66} 67 68void RenderSVGInlineText::setTextInternal(PassRefPtr<StringImpl> text) 69{ 70 RenderText::setTextInternal(text); 71 if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) 72 textRenderer->subtreeTextDidChange(this); 73} 74 75void 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.needsFullLayout()) 93 return; 94 95 // The text metrics may be influenced by style changes. 96 if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) 97 textRenderer->setNeedsLayoutAndFullPaintInvalidation(); 98} 99 100InlineTextBox* RenderSVGInlineText::createTextBox() 101{ 102 InlineTextBox* box = new SVGInlineTextBox(*this); 103 box->setHasVirtualLogicalHeight(); 104 return box; 105} 106 107LayoutRect 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 128FloatRect 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 136IntRect RenderSVGInlineText::linesBoundingBox() const 137{ 138 return enclosingIntRect(floatLinesBoundingBox()); 139} 140 141bool 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 157PositionWithAffinity 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 fragmentRect = fragmentTransform.mapRect(fragmentRect); 190 191 float distance = powf(fragmentRect.x() - absolutePoint.x(), 2) + 192 powf(fragmentRect.y() + fragmentRect.height() / 2 - absolutePoint.y(), 2); 193 194 if (distance < closestDistance) { 195 closestDistance = distance; 196 closestDistanceBox = textBox; 197 closestDistanceFragment = &fragment; 198 closestDistancePosition = fragmentRect.x(); 199 } 200 } 201 } 202 203 if (!closestDistanceFragment) 204 return createPositionWithAffinity(0, DOWNSTREAM); 205 206 int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true); 207 return createPositionWithAffinity(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); 208} 209 210void RenderSVGInlineText::updateScaledFont() 211{ 212 computeNewScaledFontForStyle(this, style(), m_scalingFactor, m_scaledFont); 213} 214 215void RenderSVGInlineText::computeNewScaledFontForStyle(RenderObject* renderer, const RenderStyle* style, float& scalingFactor, Font& scaledFont) 216{ 217 ASSERT(style); 218 ASSERT(renderer); 219 220 // Alter font-size to the right on-screen value to avoid scaling the glyphs themselves, except when GeometricPrecision is specified. 221 scalingFactor = SVGRenderingContext::calculateScreenFontSizeScalingFactor(renderer); 222 if (scalingFactor == 1 || !scalingFactor) { 223 scalingFactor = 1; 224 scaledFont = style->font(); 225 return; 226 } 227 228 if (style->fontDescription().textRendering() == GeometricPrecision) 229 scalingFactor = 1; 230 231 FontDescription fontDescription(style->fontDescription()); 232 233 Document& document = renderer->document(); 234 // FIXME: We need to better handle the case when we compute very small fonts below (below 1pt). 235 fontDescription.setComputedSize(FontSize::getComputedSizeFromSpecifiedSize(&document, scalingFactor, fontDescription.isAbsoluteSize(), fontDescription.specifiedSize(), DoNotUseSmartMinimumForFontSize)); 236 237 scaledFont = Font(fontDescription); 238 scaledFont.update(document.styleEngine()->fontSelector()); 239} 240 241} 242