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#include "SVGLengthContext.h" 29 30namespace WebCore { 31 32SVGTextChunkBuilder::SVGTextChunkBuilder() 33{ 34} 35 36void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const 37{ 38 DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ()); 39 if (!m_textBoxTransformations.contains(textBox)) { 40 transform = s_identityTransform; 41 return; 42 } 43 44 transform = m_textBoxTransformations.get(textBox); 45} 46 47void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) 48{ 49 if (lineLayoutBoxes.isEmpty()) 50 return; 51 52 bool foundStart = false; 53 unsigned lastChunkStartPosition = 0; 54 unsigned boxPosition = 0; 55 unsigned boxCount = lineLayoutBoxes.size(); 56 for (; boxPosition < boxCount; ++boxPosition) { 57 SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition]; 58 if (!textBox->startsNewTextChunk()) 59 continue; 60 61 if (!foundStart) { 62 lastChunkStartPosition = boxPosition; 63 foundStart = true; 64 } else { 65 ASSERT(boxPosition > lastChunkStartPosition); 66 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); 67 lastChunkStartPosition = boxPosition; 68 } 69 } 70 71 if (!foundStart) 72 return; 73 74 if (boxPosition - lastChunkStartPosition > 0) 75 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); 76} 77 78void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) 79{ 80 buildTextChunks(lineLayoutBoxes); 81 if (m_textChunks.isEmpty()) 82 return; 83 84 unsigned chunkCount = m_textChunks.size(); 85 for (unsigned i = 0; i < chunkCount; ++i) 86 processTextChunk(m_textChunks[i]); 87 88 m_textChunks.clear(); 89} 90 91void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount) 92{ 93 SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart]; 94 ASSERT(textBox); 95 96 RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); 97 ASSERT(textRenderer); 98 99 const RenderStyle* style = textRenderer->style(); 100 ASSERT(style); 101 102 const SVGRenderStyle* svgStyle = style->svgStyle(); 103 ASSERT(svgStyle); 104 105 // Build chunk style flags. 106 unsigned chunkStyle = SVGTextChunk::DefaultStyle; 107 108 // Handle 'direction' property. 109 if (!style->isLeftToRightDirection()) 110 chunkStyle |= SVGTextChunk::RightToLeftText; 111 112 // Handle 'writing-mode' property. 113 if (svgStyle->isVerticalWritingMode()) 114 chunkStyle |= SVGTextChunk::VerticalText; 115 116 // Handle 'text-anchor' property. 117 switch (svgStyle->textAnchor()) { 118 case TA_START: 119 break; 120 case TA_MIDDLE: 121 chunkStyle |= SVGTextChunk::MiddleAnchor; 122 break; 123 case TA_END: 124 chunkStyle |= SVGTextChunk::EndAnchor; 125 break; 126 }; 127 128 // Handle 'lengthAdjust' property. 129 float desiredTextLength = 0; 130 if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) { 131 SVGLengthContext lengthContext(textContentElement); 132 desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext); 133 134 switch (textContentElement->lengthAdjust()) { 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 155void 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 224void 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 239void 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 252void 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 266#endif // ENABLE(SVG) 267