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