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