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#include "SVGTextChunkBuilder.h" 22 23#include "SVGElement.h" 24#include "SVGInlineTextBox.h" 25#include "SVGLengthContext.h" 26 27namespace WebCore { 28 29SVGTextChunkBuilder::SVGTextChunkBuilder() 30{ 31} 32 33void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const 34{ 35 DEPRECATED_DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ()); 36 if (!m_textBoxTransformations.contains(textBox)) { 37 transform = s_identityTransform; 38 return; 39 } 40 41 transform = m_textBoxTransformations.get(textBox); 42} 43 44void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) 45{ 46 if (lineLayoutBoxes.isEmpty()) 47 return; 48 49 bool foundStart = false; 50 unsigned lastChunkStartPosition = 0; 51 unsigned boxPosition = 0; 52 unsigned boxCount = lineLayoutBoxes.size(); 53 for (; boxPosition < boxCount; ++boxPosition) { 54 SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition]; 55 if (!textBox->startsNewTextChunk()) 56 continue; 57 58 if (!foundStart) { 59 lastChunkStartPosition = boxPosition; 60 foundStart = true; 61 } else { 62 ASSERT_WITH_SECURITY_IMPLICATION(boxPosition > lastChunkStartPosition); 63 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); 64 lastChunkStartPosition = boxPosition; 65 } 66 } 67 68 if (!foundStart) 69 return; 70 71 if (boxPosition - lastChunkStartPosition > 0) 72 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); 73} 74 75void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) 76{ 77 buildTextChunks(lineLayoutBoxes); 78 if (m_textChunks.isEmpty()) 79 return; 80 81 unsigned chunkCount = m_textChunks.size(); 82 for (unsigned i = 0; i < chunkCount; ++i) 83 processTextChunk(m_textChunks[i]); 84 85 m_textChunks.clear(); 86} 87 88void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount) 89{ 90 SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart]; 91 ASSERT(textBox); 92 93 const RenderStyle& style = textBox->renderer().style(); 94 95 const SVGRenderStyle& svgStyle = style.svgStyle(); 96 97 // Build chunk style flags. 98 unsigned chunkStyle = SVGTextChunk::DefaultStyle; 99 100 // Handle 'direction' property. 101 if (!style.isLeftToRightDirection()) 102 chunkStyle |= SVGTextChunk::RightToLeftText; 103 104 // Handle 'writing-mode' property. 105 if (svgStyle.isVerticalWritingMode()) 106 chunkStyle |= SVGTextChunk::VerticalText; 107 108 // Handle 'text-anchor' property. 109 switch (svgStyle.textAnchor()) { 110 case TA_START: 111 break; 112 case TA_MIDDLE: 113 chunkStyle |= SVGTextChunk::MiddleAnchor; 114 break; 115 case TA_END: 116 chunkStyle |= SVGTextChunk::EndAnchor; 117 break; 118 }; 119 120 // Handle 'lengthAdjust' property. 121 float desiredTextLength = 0; 122 if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textBox->renderer().parent())) { 123 SVGLengthContext lengthContext(textContentElement); 124 desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext); 125 126 switch (textContentElement->lengthAdjust()) { 127 case SVGLengthAdjustUnknown: 128 break; 129 case SVGLengthAdjustSpacing: 130 chunkStyle |= SVGTextChunk::LengthAdjustSpacing; 131 break; 132 case SVGLengthAdjustSpacingAndGlyphs: 133 chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs; 134 break; 135 }; 136 } 137 138 SVGTextChunk chunk(chunkStyle, desiredTextLength); 139 140 Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); 141 for (unsigned i = boxStart; i < boxStart + boxCount; ++i) 142 boxes.append(lineLayoutBoxes[i]); 143 144 m_textChunks.append(chunk); 145} 146 147void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk) 148{ 149 bool processTextLength = chunk.hasDesiredTextLength(); 150 bool processTextAnchor = chunk.hasTextAnchor(); 151 if (!processTextAnchor && !processTextLength) 152 return; 153 154 const Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); 155 unsigned boxCount = boxes.size(); 156 if (!boxCount) 157 return; 158 159 // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes). 160 float chunkLength = 0; 161 unsigned chunkCharacters = 0; 162 chunk.calculateLength(chunkLength, chunkCharacters); 163 164 bool isVerticalText = chunk.isVerticalText(); 165 if (processTextLength) { 166 if (chunk.hasLengthAdjustSpacing()) { 167 float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters; 168 unsigned atCharacter = 0; 169 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { 170 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); 171 if (fragments.isEmpty()) 172 continue; 173 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); 174 } 175 } else { 176 ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs()); 177 float textLengthScale = chunk.desiredTextLength() / chunkLength; 178 AffineTransform spacingAndGlyphsTransform; 179 180 bool foundFirstFragment = false; 181 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { 182 SVGInlineTextBox* textBox = boxes[boxPosition]; 183 Vector<SVGTextFragment>& fragments = textBox->textFragments(); 184 if (fragments.isEmpty()) 185 continue; 186 187 if (!foundFirstFragment) { 188 foundFirstFragment = true; 189 buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform); 190 } 191 192 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform); 193 } 194 } 195 } 196 197 if (!processTextAnchor) 198 return; 199 200 // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift. 201 if (processTextLength && chunk.hasLengthAdjustSpacing()) { 202 chunkLength = 0; 203 chunkCharacters = 0; 204 chunk.calculateLength(chunkLength, chunkCharacters); 205 } 206 207 float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); 208 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { 209 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); 210 if (fragments.isEmpty()) 211 continue; 212 processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); 213 } 214} 215 216void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter) 217{ 218 unsigned fragmentCount = fragments.size(); 219 for (unsigned i = 0; i < fragmentCount; ++i) { 220 SVGTextFragment& fragment = fragments[i]; 221 222 if (isVerticalText) 223 fragment.y += textLengthShift * atCharacter; 224 else 225 fragment.x += textLengthShift * atCharacter; 226 227 atCharacter += fragment.length; 228 } 229} 230 231void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments) 232{ 233 unsigned fragmentCount = fragments.size(); 234 for (unsigned i = 0; i < fragmentCount; ++i) { 235 SVGTextFragment& fragment = fragments[i]; 236 237 if (isVerticalText) 238 fragment.y += textAnchorShift; 239 else 240 fragment.x += textAnchorShift; 241 } 242} 243 244void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform) 245{ 246 spacingAndGlyphsTransform.translate(fragment.x, fragment.y); 247 248 if (isVerticalText) 249 spacingAndGlyphsTransform.scaleNonUniform(1, scale); 250 else 251 spacingAndGlyphsTransform.scaleNonUniform(scale, 1); 252 253 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y); 254} 255 256} 257