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/dom/StyleEngine.h" 31 #include "core/editing/VisiblePosition.h" 32 #include "core/rendering/svg/RenderSVGText.h" 33 #include "core/rendering/svg/SVGInlineTextBox.h" 34 #include "core/rendering/svg/SVGRenderingContext.h" 35 36 namespace blink { 37 38 static PassRefPtr<StringImpl> applySVGWhitespaceRules(PassRefPtr<StringImpl> string, bool preserveWhiteSpace) 39 { 40 if (preserveWhiteSpace) { 41 // Spec: When xml:space="preserve", the SVG user agent will do the following using a 42 // copy of the original character data content. It will convert all newline and tab 43 // characters into space characters. Then, it will draw all space characters, including 44 // leading, trailing and multiple contiguous space characters. 45 RefPtr<StringImpl> newString = string->replace('\t', ' '); 46 newString = newString->replace('\n', ' '); 47 newString = newString->replace('\r', ' '); 48 return newString.release(); 49 } 50 51 // Spec: When xml:space="default", the SVG user agent will do the following using a 52 // copy of the original character data content. First, it will remove all newline 53 // characters. Then it will convert all tab characters into space characters. 54 // Then, it will strip off all leading and trailing space characters. 55 // Then, all contiguous space characters will be consolidated. 56 RefPtr<StringImpl> newString = string->replace('\n', StringImpl::empty()); 57 newString = newString->replace('\r', StringImpl::empty()); 58 newString = newString->replace('\t', ' '); 59 return newString.release(); 60 } 61 62 RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> string) 63 : RenderText(n, applySVGWhitespaceRules(string, false)) 64 , m_scalingFactor(1) 65 , m_layoutAttributes(this) 66 { 67 } 68 69 void RenderSVGInlineText::setTextInternal(PassRefPtr<StringImpl> text) 70 { 71 RenderText::setTextInternal(text); 72 if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) 73 textRenderer->subtreeTextDidChange(this); 74 } 75 76 void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 77 { 78 RenderText::styleDidChange(diff, oldStyle); 79 updateScaledFont(); 80 81 bool newPreserves = style() ? style()->whiteSpace() == PRE : false; 82 bool oldPreserves = oldStyle ? oldStyle->whiteSpace() == PRE : false; 83 if (oldPreserves && !newPreserves) { 84 setText(applySVGWhitespaceRules(originalText(), false), true); 85 return; 86 } 87 88 if (!oldPreserves && newPreserves) { 89 setText(applySVGWhitespaceRules(originalText(), true), true); 90 return; 91 } 92 93 if (!diff.needsFullLayout()) 94 return; 95 96 // The text metrics may be influenced by style changes. 97 if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) 98 textRenderer->setNeedsLayoutAndFullPaintInvalidation(); 99 } 100 101 InlineTextBox* RenderSVGInlineText::createTextBox() 102 { 103 InlineTextBox* box = new SVGInlineTextBox(*this); 104 box->setHasVirtualLogicalHeight(); 105 return box; 106 } 107 108 LayoutRect RenderSVGInlineText::localCaretRect(InlineBox* box, int caretOffset, LayoutUnit*) 109 { 110 if (!box || !box->isInlineTextBox()) 111 return LayoutRect(); 112 113 InlineTextBox* textBox = toInlineTextBox(box); 114 if (static_cast<unsigned>(caretOffset) < textBox->start() || static_cast<unsigned>(caretOffset) > textBox->start() + textBox->len()) 115 return LayoutRect(); 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 LayoutRect rect = textBox->localSelectionRect(caretOffset, caretOffset + 1); 120 LayoutUnit x = box->isLeftToRightDirection() ? rect.x() : rect.maxX(); 121 return LayoutRect(x, rect.y(), caretWidth, rect.height()); 122 } 123 124 LayoutRect rect = textBox->localSelectionRect(caretOffset - 1, caretOffset); 125 LayoutUnit x = box->isLeftToRightDirection() ? rect.maxX() : rect.x(); 126 return LayoutRect(x, rect.y(), caretWidth, rect.height()); 127 } 128 129 FloatRect RenderSVGInlineText::floatLinesBoundingBox() const 130 { 131 FloatRect boundingBox; 132 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) 133 boundingBox.unite(box->calculateBoundaries()); 134 return boundingBox; 135 } 136 137 IntRect RenderSVGInlineText::linesBoundingBox() const 138 { 139 return enclosingIntRect(floatLinesBoundingBox()); 140 } 141 142 bool RenderSVGInlineText::characterStartsNewTextChunk(int position) const 143 { 144 ASSERT(position >= 0); 145 ASSERT(position < static_cast<int>(textLength())); 146 147 // Each <textPath> element starts a new text chunk, regardless of any x/y values. 148 if (!position && parent()->isSVGTextPath() && !previousSibling()) 149 return true; 150 151 const SVGCharacterDataMap::const_iterator it = m_layoutAttributes.characterDataMap().find(static_cast<unsigned>(position + 1)); 152 if (it == m_layoutAttributes.characterDataMap().end()) 153 return false; 154 155 return it->value.x != SVGTextLayoutAttributes::emptyValue() || it->value.y != SVGTextLayoutAttributes::emptyValue(); 156 } 157 158 PositionWithAffinity RenderSVGInlineText::positionForPoint(const LayoutPoint& point) 159 { 160 if (!firstTextBox() || !textLength()) 161 return createPositionWithAffinity(0, DOWNSTREAM); 162 163 float baseline = m_scaledFont.fontMetrics().floatAscent(); 164 165 RenderBlock* containingBlock = this->containingBlock(); 166 ASSERT(containingBlock); 167 168 // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. 169 FloatPoint absolutePoint(point); 170 absolutePoint.moveBy(containingBlock->location()); 171 172 float closestDistance = std::numeric_limits<float>::max(); 173 float closestDistancePosition = 0; 174 const SVGTextFragment* closestDistanceFragment = 0; 175 SVGInlineTextBox* closestDistanceBox = 0; 176 177 AffineTransform fragmentTransform; 178 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { 179 if (!box->isSVGInlineTextBox()) 180 continue; 181 182 SVGInlineTextBox* textBox = toSVGInlineTextBox(box); 183 Vector<SVGTextFragment>& fragments = textBox->textFragments(); 184 185 unsigned textFragmentsSize = fragments.size(); 186 for (unsigned i = 0; i < textFragmentsSize; ++i) { 187 const SVGTextFragment& fragment = fragments.at(i); 188 FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); 189 fragment.buildFragmentTransform(fragmentTransform); 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 (style->effectiveZoom() == 1 && (scalingFactor == 1 || !scalingFactor)) { 224 scalingFactor = 1; 225 scaledFont = style->font(); 226 return; 227 } 228 229 if (style->fontDescription().textRendering() == 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); 239 scaledFont.update(document.styleEngine()->fontSelector()); 240 } 241 242 } 243