Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  */
     19 
     20 #include "config.h"
     21 
     22 #if ENABLE(SVG)
     23 #include "SVGTextChunkBuilder.h"
     24 
     25 #include "RenderSVGInlineText.h"
     26 #include "SVGElement.h"
     27 #include "SVGInlineTextBox.h"
     28 
     29 namespace WebCore {
     30 
     31 SVGTextChunkBuilder::SVGTextChunkBuilder()
     32 {
     33 }
     34 
     35 void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
     36 {
     37     DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
     38     if (!m_textBoxTransformations.contains(textBox)) {
     39         transform = s_identityTransform;
     40         return;
     41     }
     42 
     43     transform = m_textBoxTransformations.get(textBox);
     44 }
     45 
     46 void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
     47 {
     48     if (lineLayoutBoxes.isEmpty())
     49         return;
     50 
     51     bool foundStart = false;
     52     unsigned lastChunkStartPosition = 0;
     53     unsigned boxPosition = 0;
     54     unsigned boxCount = lineLayoutBoxes.size();
     55     for (; boxPosition < boxCount; ++boxPosition) {
     56         SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
     57         if (!textBox->startsNewTextChunk())
     58             continue;
     59 
     60         if (!foundStart) {
     61             lastChunkStartPosition = boxPosition;
     62             foundStart = true;
     63         } else {
     64             ASSERT(boxPosition > lastChunkStartPosition);
     65             addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
     66             lastChunkStartPosition = boxPosition;
     67         }
     68     }
     69 
     70     if (!foundStart)
     71         return;
     72 
     73     if (boxPosition - lastChunkStartPosition > 0)
     74         addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
     75 }
     76 
     77 void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
     78 {
     79     buildTextChunks(lineLayoutBoxes);
     80     if (m_textChunks.isEmpty())
     81         return;
     82 
     83     unsigned chunkCount = m_textChunks.size();
     84     for (unsigned i = 0; i < chunkCount; ++i)
     85         processTextChunk(m_textChunks[i]);
     86 
     87     m_textChunks.clear();
     88 }
     89 
     90 void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
     91 {
     92     SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
     93     ASSERT(textBox);
     94 
     95     RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer());
     96     ASSERT(textRenderer);
     97 
     98     const RenderStyle* style = textRenderer->style();
     99     ASSERT(style);
    100 
    101     const SVGRenderStyle* svgStyle = style->svgStyle();
    102     ASSERT(svgStyle);
    103 
    104     // Build chunk style flags.
    105     unsigned chunkStyle = SVGTextChunk::DefaultStyle;
    106 
    107     // Handle 'direction' property.
    108     if (!style->isLeftToRightDirection())
    109         chunkStyle |= SVGTextChunk::RightToLeftText;
    110 
    111     // Handle 'writing-mode' property.
    112     if (svgStyle->isVerticalWritingMode())
    113         chunkStyle |= SVGTextChunk::VerticalText;
    114 
    115     // Handle 'text-anchor' property.
    116     switch (svgStyle->textAnchor()) {
    117     case TA_START:
    118         break;
    119     case TA_MIDDLE:
    120         chunkStyle |= SVGTextChunk::MiddleAnchor;
    121         break;
    122     case TA_END:
    123         chunkStyle |= SVGTextChunk::EndAnchor;
    124         break;
    125     };
    126 
    127     // Handle 'lengthAdjust' property.
    128     float desiredTextLength = 0;
    129     if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) {
    130         desiredTextLength = textContentElement->specifiedTextLength().value(textContentElement);
    131 
    132         switch (static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust())) {
    133         case SVGTextContentElement::LENGTHADJUST_UNKNOWN:
    134             break;
    135         case SVGTextContentElement::LENGTHADJUST_SPACING:
    136             chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
    137             break;
    138         case SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS:
    139             chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs;
    140             break;
    141         };
    142     }
    143 
    144     SVGTextChunk chunk(chunkStyle, desiredTextLength);
    145 
    146     Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
    147     for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
    148         boxes.append(lineLayoutBoxes[i]);
    149 
    150     m_textChunks.append(chunk);
    151 }
    152 
    153 void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
    154 {
    155     bool processTextLength = chunk.hasDesiredTextLength();
    156     bool processTextAnchor = chunk.hasTextAnchor();
    157     if (!processTextAnchor && !processTextLength)
    158         return;
    159 
    160     const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
    161     unsigned boxCount = boxes.size();
    162     if (!boxCount)
    163         return;
    164 
    165     // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
    166     float chunkLength = 0;
    167     unsigned chunkCharacters = 0;
    168     chunk.calculateLength(chunkLength, chunkCharacters);
    169 
    170     bool isVerticalText = chunk.isVerticalText();
    171     if (processTextLength) {
    172         if (chunk.hasLengthAdjustSpacing()) {
    173             float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
    174             unsigned atCharacter = 0;
    175             for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    176                 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
    177                 if (fragments.isEmpty())
    178                     continue;
    179                 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
    180             }
    181         } else {
    182             ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs());
    183             float textLengthScale = chunk.desiredTextLength() / chunkLength;
    184             AffineTransform spacingAndGlyphsTransform;
    185 
    186             bool foundFirstFragment = false;
    187             for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    188                 SVGInlineTextBox* textBox = boxes[boxPosition];
    189                 Vector<SVGTextFragment>& fragments = textBox->textFragments();
    190                 if (fragments.isEmpty())
    191                     continue;
    192 
    193                 if (!foundFirstFragment) {
    194                     foundFirstFragment = true;
    195                     buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
    196                 }
    197 
    198                 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
    199             }
    200         }
    201     }
    202 
    203     if (!processTextAnchor)
    204         return;
    205 
    206     // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
    207     if (processTextLength && chunk.hasLengthAdjustSpacing()) {
    208         chunkLength = 0;
    209         chunkCharacters = 0;
    210         chunk.calculateLength(chunkLength, chunkCharacters);
    211     }
    212 
    213     float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
    214     for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    215         Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
    216         if (fragments.isEmpty())
    217             continue;
    218         processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
    219     }
    220 }
    221 
    222 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
    223 {
    224     unsigned fragmentCount = fragments.size();
    225     for (unsigned i = 0; i < fragmentCount; ++i) {
    226         SVGTextFragment& fragment = fragments[i];
    227 
    228         if (isVerticalText)
    229             fragment.y += textLengthShift * atCharacter;
    230         else
    231             fragment.x += textLengthShift * atCharacter;
    232 
    233         atCharacter += fragment.length;
    234     }
    235 }
    236 
    237 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
    238 {
    239     unsigned fragmentCount = fragments.size();
    240     for (unsigned i = 0; i < fragmentCount; ++i) {
    241         SVGTextFragment& fragment = fragments[i];
    242 
    243         if (isVerticalText)
    244             fragment.y += textAnchorShift;
    245         else
    246             fragment.x += textAnchorShift;
    247     }
    248 }
    249 
    250 void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
    251 {
    252     spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
    253 
    254     if (isVerticalText)
    255         spacingAndGlyphsTransform.scaleNonUniform(1, scale);
    256     else
    257         spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
    258 
    259     spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
    260 }
    261 
    262 }
    263 
    264 #endif // ENABLE(SVG)
    265