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