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 #include "core/rendering/svg/SVGTextChunkBuilder.h"
     23 
     24 #include "core/rendering/svg/RenderSVGInlineText.h"
     25 #include "core/rendering/svg/SVGInlineTextBox.h"
     26 #include "core/svg/SVGLengthContext.h"
     27 
     28 namespace WebCore {
     29 
     30 SVGTextChunkBuilder::SVGTextChunkBuilder()
     31 {
     32 }
     33 
     34 void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
     35 {
     36     DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
     37     if (!m_textBoxTransformations.contains(textBox)) {
     38         transform = s_identityTransform;
     39         return;
     40     }
     41 
     42     transform = m_textBoxTransformations.get(textBox);
     43 }
     44 
     45 void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
     46 {
     47     if (lineLayoutBoxes.isEmpty())
     48         return;
     49 
     50     bool foundStart = false;
     51     unsigned lastChunkStartPosition = 0;
     52     unsigned boxPosition = 0;
     53     unsigned boxCount = lineLayoutBoxes.size();
     54     for (; boxPosition < boxCount; ++boxPosition) {
     55         SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
     56         if (!textBox->startsNewTextChunk())
     57             continue;
     58 
     59         if (!foundStart) {
     60             lastChunkStartPosition = boxPosition;
     61             foundStart = true;
     62         } else {
     63             ASSERT(boxPosition > lastChunkStartPosition);
     64             addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
     65             lastChunkStartPosition = boxPosition;
     66         }
     67     }
     68 
     69     if (!foundStart)
     70         return;
     71 
     72     if (boxPosition - lastChunkStartPosition > 0)
     73         addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
     74 }
     75 
     76 void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
     77 {
     78     buildTextChunks(lineLayoutBoxes);
     79     if (m_textChunks.isEmpty())
     80         return;
     81 
     82     unsigned chunkCount = m_textChunks.size();
     83     for (unsigned i = 0; i < chunkCount; ++i)
     84         processTextChunk(m_textChunks[i]);
     85 
     86     m_textChunks.clear();
     87 }
     88 
     89 void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
     90 {
     91     SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
     92     ASSERT(textBox);
     93 
     94     RenderSVGInlineText& textRenderer = toRenderSVGInlineText(textBox->textRenderer());
     95 
     96     const RenderStyle* style = toRenderSVGInlineText(textBox->textRenderer()).style();
     97     ASSERT(style);
     98 
     99     const SVGRenderStyle* svgStyle = style->svgStyle();
    100     ASSERT(svgStyle);
    101 
    102     // Build chunk style flags.
    103     unsigned chunkStyle = SVGTextChunk::DefaultStyle;
    104 
    105     // Handle 'direction' property.
    106     if (!style->isLeftToRightDirection())
    107         chunkStyle |= SVGTextChunk::RightToLeftText;
    108 
    109     // Handle 'writing-mode' property.
    110     if (svgStyle->isVerticalWritingMode())
    111         chunkStyle |= SVGTextChunk::VerticalText;
    112 
    113     // Handle 'text-anchor' property.
    114     switch (svgStyle->textAnchor()) {
    115     case TA_START:
    116         break;
    117     case TA_MIDDLE:
    118         chunkStyle |= SVGTextChunk::MiddleAnchor;
    119         break;
    120     case TA_END:
    121         chunkStyle |= SVGTextChunk::EndAnchor;
    122         break;
    123     };
    124 
    125     // Handle 'lengthAdjust' property.
    126     float desiredTextLength = 0;
    127     if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer.parent())) {
    128         SVGLengthContext lengthContext(textContentElement);
    129         if (textContentElement->textLengthIsSpecifiedByUser())
    130             desiredTextLength = textContentElement->textLength()->currentValue()->value(lengthContext);
    131         else
    132             desiredTextLength = 0;
    133 
    134         switch (textContentElement->lengthAdjust()->currentValue()->enumValue()) {
    135         case SVGLengthAdjustUnknown:
    136             break;
    137         case SVGLengthAdjustSpacing:
    138             chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
    139             break;
    140         case SVGLengthAdjustSpacingAndGlyphs:
    141             chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs;
    142             break;
    143         };
    144     }
    145 
    146     SVGTextChunk chunk(chunkStyle, desiredTextLength);
    147 
    148     Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
    149     for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
    150         boxes.append(lineLayoutBoxes[i]);
    151 
    152     m_textChunks.append(chunk);
    153 }
    154 
    155 void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
    156 {
    157     bool processTextLength = chunk.hasDesiredTextLength();
    158     bool processTextAnchor = chunk.hasTextAnchor();
    159     if (!processTextAnchor && !processTextLength)
    160         return;
    161 
    162     const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
    163     unsigned boxCount = boxes.size();
    164     if (!boxCount)
    165         return;
    166 
    167     // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
    168     float chunkLength = 0;
    169     unsigned chunkCharacters = 0;
    170     chunk.calculateLength(chunkLength, chunkCharacters);
    171 
    172     bool isVerticalText = chunk.isVerticalText();
    173     if (processTextLength) {
    174         if (chunk.hasLengthAdjustSpacing()) {
    175             float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
    176             unsigned atCharacter = 0;
    177             for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    178                 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
    179                 if (fragments.isEmpty())
    180                     continue;
    181                 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
    182             }
    183         } else {
    184             ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs());
    185             float textLengthScale = chunk.desiredTextLength() / chunkLength;
    186             AffineTransform spacingAndGlyphsTransform;
    187 
    188             bool foundFirstFragment = false;
    189             for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    190                 SVGInlineTextBox* textBox = boxes[boxPosition];
    191                 Vector<SVGTextFragment>& fragments = textBox->textFragments();
    192                 if (fragments.isEmpty())
    193                     continue;
    194 
    195                 if (!foundFirstFragment) {
    196                     foundFirstFragment = true;
    197                     buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
    198                 }
    199 
    200                 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
    201             }
    202         }
    203     }
    204 
    205     if (!processTextAnchor)
    206         return;
    207 
    208     // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
    209     if (processTextLength && chunk.hasLengthAdjustSpacing()) {
    210         chunkLength = 0;
    211         chunkCharacters = 0;
    212         chunk.calculateLength(chunkLength, chunkCharacters);
    213     }
    214 
    215     float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
    216     for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    217         Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
    218         if (fragments.isEmpty())
    219             continue;
    220         processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
    221     }
    222 }
    223 
    224 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
    225 {
    226     unsigned fragmentCount = fragments.size();
    227     for (unsigned i = 0; i < fragmentCount; ++i) {
    228         SVGTextFragment& fragment = fragments[i];
    229 
    230         if (isVerticalText)
    231             fragment.y += textLengthShift * atCharacter;
    232         else
    233             fragment.x += textLengthShift * atCharacter;
    234 
    235         atCharacter += fragment.length;
    236     }
    237 }
    238 
    239 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
    240 {
    241     unsigned fragmentCount = fragments.size();
    242     for (unsigned i = 0; i < fragmentCount; ++i) {
    243         SVGTextFragment& fragment = fragments[i];
    244 
    245         if (isVerticalText)
    246             fragment.y += textAnchorShift;
    247         else
    248             fragment.x += textAnchorShift;
    249     }
    250 }
    251 
    252 void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
    253 {
    254     spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
    255 
    256     if (isVerticalText)
    257         spacingAndGlyphsTransform.scaleNonUniform(1, scale);
    258     else
    259         spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
    260 
    261     spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
    262 }
    263 
    264 }
    265