Home | History | Annotate | Download | only in svg
      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