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