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 ASSERT(textRenderer); 96 97 const RenderStyle* style = textRenderer->style(); 98 ASSERT(style); 99 100 const SVGRenderStyle* svgStyle = style->svgStyle(); 101 ASSERT(svgStyle); 102 103 // Build chunk style flags. 104 unsigned chunkStyle = SVGTextChunk::DefaultStyle; 105 106 // Handle 'direction' property. 107 if (!style->isLeftToRightDirection()) 108 chunkStyle |= SVGTextChunk::RightToLeftText; 109 110 // Handle 'writing-mode' property. 111 if (svgStyle->isVerticalWritingMode()) 112 chunkStyle |= SVGTextChunk::VerticalText; 113 114 // Handle 'text-anchor' property. 115 switch (svgStyle->textAnchor()) { 116 case TA_START: 117 break; 118 case TA_MIDDLE: 119 chunkStyle |= SVGTextChunk::MiddleAnchor; 120 break; 121 case TA_END: 122 chunkStyle |= SVGTextChunk::EndAnchor; 123 break; 124 }; 125 126 // Handle 'lengthAdjust' property. 127 float desiredTextLength = 0; 128 if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) { 129 SVGLengthContext lengthContext(textContentElement); 130 desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext); 131 132 switch (textContentElement->lengthAdjustCurrentValue()) { 133 case SVGLengthAdjustUnknown: 134 break; 135 case SVGLengthAdjustSpacing: 136 chunkStyle |= SVGTextChunk::LengthAdjustSpacing; 137 break; 138 case SVGLengthAdjustSpacingAndGlyphs: 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