| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) Research In Motion Limited 2010. All rights reserved. | 2 * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
| 3 * | 3 * |
| 4 * This library is free software; you can redistribute it and/or | 4 * This library is free software; you can redistribute it and/or |
| 5 * modify it under the terms of the GNU Library General Public | 5 * modify it under the terms of the GNU Library General Public |
| 6 * License as published by the Free Software Foundation; either | 6 * License as published by the Free Software Foundation; either |
| 7 * version 2 of the License, or (at your option) any later version. | 7 * version 2 of the License, or (at your option) any later version. |
| 8 * | 8 * |
| 9 * This library is distributed in the hope that it will be useful, | 9 * This library is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| (...skipping 10 matching lines...) Expand all Loading... |
| 21 #include "core/layout/svg/SVGTextChunkBuilder.h" | 21 #include "core/layout/svg/SVGTextChunkBuilder.h" |
| 22 | 22 |
| 23 #include "core/layout/svg/LayoutSVGInlineText.h" | 23 #include "core/layout/svg/LayoutSVGInlineText.h" |
| 24 #include "core/layout/svg/SVGTextChunk.h" | 24 #include "core/layout/svg/SVGTextChunk.h" |
| 25 #include "core/layout/svg/line/SVGInlineTextBox.h" | 25 #include "core/layout/svg/line/SVGInlineTextBox.h" |
| 26 #include "core/svg/SVGLengthContext.h" | 26 #include "core/svg/SVGLengthContext.h" |
| 27 #include "core/svg/SVGTextContentElement.h" | 27 #include "core/svg/SVGTextContentElement.h" |
| 28 | 28 |
| 29 namespace blink { | 29 namespace blink { |
| 30 | 30 |
| 31 SVGTextChunkBuilder::SVGTextChunkBuilder() | 31 namespace { |
| 32 |
| 33 SVGTextChunk createTextChunk(const SVGInlineTextBox& textBox) |
| 32 { | 34 { |
| 33 } | 35 LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(textBox.layout
Object()); |
| 34 | 36 const ComputedStyle& style = textLayoutObject.styleRef(); |
| 35 void SVGTextChunkBuilder::processTextChunks(const Vector<SVGInlineTextBox*>& lin
eLayoutBoxes) | |
| 36 { | |
| 37 if (lineLayoutBoxes.isEmpty()) | |
| 38 return; | |
| 39 | |
| 40 bool foundStart = false; | |
| 41 unsigned lastChunkStartPosition = 0; | |
| 42 unsigned boxPosition = 0; | |
| 43 unsigned boxCount = lineLayoutBoxes.size(); | |
| 44 for (; boxPosition < boxCount; ++boxPosition) { | |
| 45 SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition]; | |
| 46 if (!textBox->startsNewTextChunk()) | |
| 47 continue; | |
| 48 | |
| 49 if (!foundStart) { | |
| 50 foundStart = true; | |
| 51 } else { | |
| 52 ASSERT(boxPosition > lastChunkStartPosition); | |
| 53 handleTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition
); | |
| 54 } | |
| 55 lastChunkStartPosition = boxPosition; | |
| 56 } | |
| 57 | |
| 58 if (!foundStart) | |
| 59 return; | |
| 60 | |
| 61 if (boxPosition - lastChunkStartPosition > 0) | |
| 62 handleTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition); | |
| 63 } | |
| 64 | |
| 65 SVGTextPathChunkBuilder::SVGTextPathChunkBuilder() | |
| 66 : SVGTextChunkBuilder() | |
| 67 , m_totalLength(0) | |
| 68 , m_totalCharacters(0) | |
| 69 , m_totalTextAnchorShift(0) | |
| 70 { | |
| 71 } | |
| 72 | |
| 73 static SVGTextChunk createTextChunk(const Vector<SVGInlineTextBox*>& lineLayoutB
oxes, unsigned boxStart, unsigned boxCount) | |
| 74 { | |
| 75 SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart]; | |
| 76 ASSERT(textBox); | |
| 77 | |
| 78 LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(textBox->layou
tObject()); | |
| 79 | |
| 80 const ComputedStyle& style = toLayoutSVGInlineText(textBox->layoutObject()).
styleRef(); | |
| 81 | |
| 82 const SVGComputedStyle& svgStyle = style.svgStyle(); | 37 const SVGComputedStyle& svgStyle = style.svgStyle(); |
| 83 | 38 |
| 84 // Build chunk style flags. | 39 // Build chunk style flags. |
| 85 unsigned chunkStyle = SVGTextChunk::DefaultStyle; | 40 unsigned chunkStyle = SVGTextChunk::DefaultStyle; |
| 86 | 41 |
| 87 // Handle 'direction' property. | 42 // Handle 'direction' property. |
| 88 if (!style.isLeftToRightDirection()) | 43 if (!style.isLeftToRightDirection()) |
| 89 chunkStyle |= SVGTextChunk::RightToLeftText; | 44 chunkStyle |= SVGTextChunk::RightToLeftText; |
| 90 | 45 |
| 91 // Handle 'writing-mode' property. | 46 // Handle 'writing-mode' property. |
| 92 if (svgStyle.isVerticalWritingMode()) | 47 if (svgStyle.isVerticalWritingMode()) |
| 93 chunkStyle |= SVGTextChunk::VerticalText; | 48 chunkStyle |= SVGTextChunk::VerticalText; |
| 94 | 49 |
| 95 // Handle 'text-anchor' property. | 50 // Handle 'text-anchor' property. |
| 96 switch (svgStyle.textAnchor()) { | 51 switch (svgStyle.textAnchor()) { |
| 97 case TA_START: | 52 case TA_START: |
| 98 break; | 53 break; |
| 99 case TA_MIDDLE: | 54 case TA_MIDDLE: |
| 100 chunkStyle |= SVGTextChunk::MiddleAnchor; | 55 chunkStyle |= SVGTextChunk::MiddleAnchor; |
| 101 break; | 56 break; |
| 102 case TA_END: | 57 case TA_END: |
| 103 chunkStyle |= SVGTextChunk::EndAnchor; | 58 chunkStyle |= SVGTextChunk::EndAnchor; |
| 104 break; | 59 break; |
| 105 }; | 60 } |
| 106 | 61 |
| 107 // Handle 'lengthAdjust' property. | 62 // Handle 'lengthAdjust' property. |
| 108 float desiredTextLength = 0; | 63 float desiredTextLength = 0; |
| 109 if (SVGTextContentElement* textContentElement = SVGTextContentElement::eleme
ntFromLayoutObject(textLayoutObject.parent())) { | 64 if (SVGTextContentElement* textContentElement = SVGTextContentElement::eleme
ntFromLayoutObject(textLayoutObject.parent())) { |
| 110 SVGLengthContext lengthContext(textContentElement); | 65 SVGLengthContext lengthContext(textContentElement); |
| 111 if (textContentElement->textLengthIsSpecifiedByUser()) | 66 if (textContentElement->textLengthIsSpecifiedByUser()) |
| 112 desiredTextLength = textContentElement->textLength()->currentValue()
->value(lengthContext); | 67 desiredTextLength = textContentElement->textLength()->currentValue()
->value(lengthContext); |
| 113 else | 68 else |
| 114 desiredTextLength = 0; | 69 desiredTextLength = 0; |
| 115 | 70 |
| 116 switch (textContentElement->lengthAdjust()->currentValue()->enumValue())
{ | 71 switch (textContentElement->lengthAdjust()->currentValue()->enumValue())
{ |
| 117 case SVGLengthAdjustUnknown: | 72 case SVGLengthAdjustUnknown: |
| 118 break; | 73 break; |
| 119 case SVGLengthAdjustSpacing: | 74 case SVGLengthAdjustSpacing: |
| 120 chunkStyle |= SVGTextChunk::LengthAdjustSpacing; | 75 chunkStyle |= SVGTextChunk::LengthAdjustSpacing; |
| 121 break; | 76 break; |
| 122 case SVGLengthAdjustSpacingAndGlyphs: | 77 case SVGLengthAdjustSpacingAndGlyphs: |
| 123 chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs; | 78 chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs; |
| 124 break; | 79 break; |
| 125 }; | 80 } |
| 126 } | 81 } |
| 127 | 82 |
| 128 SVGTextChunk chunk(chunkStyle, desiredTextLength); | 83 return SVGTextChunk(chunkStyle, desiredTextLength); |
| 129 | |
| 130 Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); | |
| 131 for (unsigned i = boxStart; i < boxStart + boxCount; ++i) | |
| 132 boxes.append(lineLayoutBoxes[i]); | |
| 133 | |
| 134 return chunk; | |
| 135 } | 84 } |
| 136 | 85 |
| 137 void SVGTextPathChunkBuilder::handleTextChunk(const Vector<SVGInlineTextBox*>& b
oxes, unsigned boxStart, unsigned boxEnd) | 86 class ChunkLengthAccumulator { |
| 87 public: |
| 88 ChunkLengthAccumulator(bool isVertical) |
| 89 : m_numCharacters(0) |
| 90 , m_length(0) |
| 91 , m_isVertical(isVertical) |
| 92 { |
| 93 } |
| 94 |
| 95 typedef Vector<SVGInlineTextBox*>::const_iterator BoxListConstIterator; |
| 96 |
| 97 void processRange(BoxListConstIterator boxStart, BoxListConstIterator boxEnd
); |
| 98 void reset() |
| 99 { |
| 100 m_numCharacters = 0; |
| 101 m_length = 0; |
| 102 } |
| 103 |
| 104 float length() const { return m_length; } |
| 105 unsigned numCharacters() const { return m_numCharacters; } |
| 106 |
| 107 private: |
| 108 unsigned m_numCharacters; |
| 109 float m_length; |
| 110 const bool m_isVertical; |
| 111 }; |
| 112 |
| 113 void ChunkLengthAccumulator::processRange(BoxListConstIterator boxStart, BoxList
ConstIterator boxEnd) |
| 138 { | 114 { |
| 139 SVGTextChunk chunk = createTextChunk(boxes, boxStart, boxEnd - boxStart); | 115 SVGTextFragment* lastFragment = nullptr; |
| 116 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { |
| 117 for (SVGTextFragment& fragment : (*boxIter)->textFragments()) { |
| 118 m_numCharacters += fragment.length; |
| 140 | 119 |
| 141 float length = 0; | 120 if (m_isVertical) |
| 142 unsigned characters = 0; | 121 m_length += fragment.height; |
| 143 chunk.calculateLength(length, characters); | 122 else |
| 123 m_length += fragment.width; |
| 124 |
| 125 if (!lastFragment) { |
| 126 lastFragment = &fragment; |
| 127 continue; |
| 128 } |
| 129 |
| 130 // Respect gap between chunks. |
| 131 if (m_isVertical) |
| 132 m_length += fragment.y - (lastFragment->y + lastFragment->height
); |
| 133 else |
| 134 m_length += fragment.x - (lastFragment->x + lastFragment->width)
; |
| 135 |
| 136 lastFragment = &fragment; |
| 137 } |
| 138 } |
| 139 } |
| 140 |
| 141 } |
| 142 |
| 143 SVGTextChunkBuilder::SVGTextChunkBuilder() |
| 144 { |
| 145 } |
| 146 |
| 147 void SVGTextChunkBuilder::processTextChunks(const Vector<SVGInlineTextBox*>& lin
eLayoutBoxes) |
| 148 { |
| 149 if (lineLayoutBoxes.isEmpty()) |
| 150 return; |
| 151 |
| 152 bool foundStart = false; |
| 153 auto boxIter = lineLayoutBoxes.begin(); |
| 154 auto endBox = lineLayoutBoxes.end(); |
| 155 auto chunkStartBox = boxIter; |
| 156 for (; boxIter != endBox; ++boxIter) { |
| 157 if (!(*boxIter)->startsNewTextChunk()) |
| 158 continue; |
| 159 |
| 160 if (!foundStart) { |
| 161 foundStart = true; |
| 162 } else { |
| 163 ASSERT(boxIter != chunkStartBox); |
| 164 handleTextChunk(chunkStartBox, boxIter); |
| 165 } |
| 166 chunkStartBox = boxIter; |
| 167 } |
| 168 |
| 169 if (!foundStart) |
| 170 return; |
| 171 |
| 172 if (boxIter != chunkStartBox) |
| 173 handleTextChunk(chunkStartBox, boxIter); |
| 174 } |
| 175 |
| 176 SVGTextPathChunkBuilder::SVGTextPathChunkBuilder() |
| 177 : SVGTextChunkBuilder() |
| 178 , m_totalLength(0) |
| 179 , m_totalCharacters(0) |
| 180 , m_totalTextAnchorShift(0) |
| 181 { |
| 182 } |
| 183 |
| 184 void SVGTextPathChunkBuilder::handleTextChunk(BoxListConstIterator boxStart, Box
ListConstIterator boxEnd) |
| 185 { |
| 186 SVGTextChunk chunk = createTextChunk(**boxStart); |
| 187 |
| 188 ChunkLengthAccumulator lengthAccumulator(chunk.isVerticalText()); |
| 189 lengthAccumulator.processRange(boxStart, boxEnd); |
| 144 | 190 |
| 145 // Handle text-anchor as additional start offset for text paths. | 191 // Handle text-anchor as additional start offset for text paths. |
| 146 m_totalTextAnchorShift += chunk.calculateTextAnchorShift(length); | 192 m_totalTextAnchorShift += chunk.calculateTextAnchorShift(lengthAccumulator.l
ength()); |
| 147 | 193 |
| 148 m_totalLength += length; | 194 m_totalLength += lengthAccumulator.length(); |
| 149 m_totalCharacters += characters; | 195 m_totalCharacters += lengthAccumulator.numCharacters(); |
| 150 } | 196 } |
| 151 | 197 |
| 152 static void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, con
st SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform) | 198 static void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, con
st SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform) |
| 153 { | 199 { |
| 154 spacingAndGlyphsTransform.translate(fragment.x, fragment.y); | 200 spacingAndGlyphsTransform.translate(fragment.x, fragment.y); |
| 155 | 201 |
| 156 if (isVerticalText) | 202 if (isVerticalText) |
| 157 spacingAndGlyphsTransform.scaleNonUniform(1, scale); | 203 spacingAndGlyphsTransform.scaleNonUniform(1, scale); |
| 158 else | 204 else |
| 159 spacingAndGlyphsTransform.scaleNonUniform(scale, 1); | 205 spacingAndGlyphsTransform.scaleNonUniform(scale, 1); |
| 160 | 206 |
| 161 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y); | 207 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y); |
| 162 } | 208 } |
| 163 | 209 |
| 164 void SVGTextChunkBuilder::handleTextChunk(const Vector<SVGInlineTextBox*>& lineB
oxes, unsigned boxStart, unsigned boxEnd) | 210 void SVGTextChunkBuilder::handleTextChunk(BoxListConstIterator boxStart, BoxList
ConstIterator boxEnd) |
| 165 { | 211 { |
| 166 SVGTextChunk chunk = createTextChunk(lineBoxes, boxStart, boxEnd - boxStart)
; | 212 SVGTextChunk chunk = createTextChunk(**boxStart); |
| 167 | 213 |
| 168 bool processTextLength = chunk.hasDesiredTextLength(); | 214 bool processTextLength = chunk.hasDesiredTextLength(); |
| 169 bool processTextAnchor = chunk.hasTextAnchor(); | 215 bool processTextAnchor = chunk.hasTextAnchor(); |
| 170 if (!processTextAnchor && !processTextLength) | 216 if (!processTextAnchor && !processTextLength) |
| 171 return; | 217 return; |
| 172 | 218 |
| 173 const Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); | 219 bool isVerticalText = chunk.isVerticalText(); |
| 174 unsigned boxCount = boxes.size(); | |
| 175 if (!boxCount) | |
| 176 return; | |
| 177 | 220 |
| 178 // Calculate absolute length of whole text chunk (starting from text box 'st
art', spanning 'length' text boxes). | 221 // Calculate absolute length of whole text chunk (starting from text box 'st
art', spanning 'length' text boxes). |
| 179 float chunkLength = 0; | 222 ChunkLengthAccumulator lengthAccumulator(isVerticalText); |
| 180 unsigned chunkCharacters = 0; | 223 lengthAccumulator.processRange(boxStart, boxEnd); |
| 181 chunk.calculateLength(chunkLength, chunkCharacters); | |
| 182 | 224 |
| 183 bool isVerticalText = chunk.isVerticalText(); | |
| 184 if (processTextLength) { | 225 if (processTextLength) { |
| 226 float chunkLength = lengthAccumulator.length(); |
| 185 if (chunk.hasLengthAdjustSpacing()) { | 227 if (chunk.hasLengthAdjustSpacing()) { |
| 186 float textLengthShift = (chunk.desiredTextLength() - chunkLength) /
chunkCharacters; | 228 float textLengthShift = (chunk.desiredTextLength() - chunkLength) /
lengthAccumulator.numCharacters(); |
| 187 unsigned atCharacter = 0; | 229 unsigned atCharacter = 0; |
| 188 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition
) { | 230 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { |
| 189 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFra
gments(); | 231 Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments()
; |
| 190 if (fragments.isEmpty()) | 232 if (fragments.isEmpty()) |
| 191 continue; | 233 continue; |
| 192 processTextLengthSpacingCorrection(isVerticalText, textLengthShi
ft, fragments, atCharacter); | 234 processTextLengthSpacingCorrection(isVerticalText, textLengthShi
ft, fragments, atCharacter); |
| 193 } | 235 } |
| 236 |
| 237 // Fragments have been adjusted, we have to recalculate the chunk |
| 238 // length, to be able to apply the text-anchor shift. |
| 239 if (processTextAnchor) { |
| 240 lengthAccumulator.reset(); |
| 241 lengthAccumulator.processRange(boxStart, boxEnd); |
| 242 } |
| 194 } else { | 243 } else { |
| 195 ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs()); | 244 ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs()); |
| 196 float textLengthScale = chunk.desiredTextLength() / chunkLength; | 245 float textLengthScale = chunk.desiredTextLength() / chunkLength; |
| 197 AffineTransform spacingAndGlyphsTransform; | 246 AffineTransform spacingAndGlyphsTransform; |
| 198 | 247 |
| 199 bool foundFirstFragment = false; | 248 bool foundFirstFragment = false; |
| 200 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition
) { | 249 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { |
| 201 SVGInlineTextBox* textBox = boxes[boxPosition]; | 250 SVGInlineTextBox* textBox = *boxIter; |
| 202 Vector<SVGTextFragment>& fragments = textBox->textFragments(); | 251 Vector<SVGTextFragment>& fragments = textBox->textFragments(); |
| 203 if (fragments.isEmpty()) | 252 if (fragments.isEmpty()) |
| 204 continue; | 253 continue; |
| 205 | 254 |
| 206 if (!foundFirstFragment) { | 255 if (!foundFirstFragment) { |
| 207 foundFirstFragment = true; | 256 foundFirstFragment = true; |
| 208 buildSpacingAndGlyphsTransform(isVerticalText, textLengthSca
le, fragments.first(), spacingAndGlyphsTransform); | 257 buildSpacingAndGlyphsTransform(isVerticalText, textLengthSca
le, fragments.first(), spacingAndGlyphsTransform); |
| 209 } | 258 } |
| 210 | 259 |
| 211 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform)
; | 260 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform)
; |
| 212 } | 261 } |
| 213 } | 262 } |
| 214 } | 263 } |
| 215 | 264 |
| 216 if (!processTextAnchor) | 265 if (!processTextAnchor) |
| 217 return; | 266 return; |
| 218 | 267 |
| 219 // If we previously applied a lengthAdjust="spacing" correction, we have to
recalculate the chunk length, to be able to apply the text-anchor shift. | 268 float textAnchorShift = chunk.calculateTextAnchorShift(lengthAccumulator.len
gth()); |
| 220 if (processTextLength && chunk.hasLengthAdjustSpacing()) { | 269 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { |
| 221 chunkLength = 0; | 270 Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments(); |
| 222 chunkCharacters = 0; | |
| 223 chunk.calculateLength(chunkLength, chunkCharacters); | |
| 224 } | |
| 225 | |
| 226 float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); | |
| 227 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { | |
| 228 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments()
; | |
| 229 if (fragments.isEmpty()) | 271 if (fragments.isEmpty()) |
| 230 continue; | 272 continue; |
| 231 processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); | 273 processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); |
| 232 } | 274 } |
| 233 } | 275 } |
| 234 | 276 |
| 235 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText
, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharact
er) | 277 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText
, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharact
er) |
| 236 { | 278 { |
| 237 unsigned fragmentCount = fragments.size(); | 279 unsigned fragmentCount = fragments.size(); |
| 238 for (unsigned i = 0; i < fragmentCount; ++i) { | 280 for (unsigned i = 0; i < fragmentCount; ++i) { |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 271 continue; | 313 continue; |
| 272 | 314 |
| 273 for (SVGTextFragment& fragment : textBox->textFragments()) { | 315 for (SVGTextFragment& fragment : textBox->textFragments()) { |
| 274 ASSERT(fragment.lengthAdjustTransform.isIdentity()); | 316 ASSERT(fragment.lengthAdjustTransform.isIdentity()); |
| 275 fragment.lengthAdjustTransform = textBoxTransformation; | 317 fragment.lengthAdjustTransform = textBoxTransformation; |
| 276 } | 318 } |
| 277 } | 319 } |
| 278 } | 320 } |
| 279 | 321 |
| 280 } | 322 } |
| OLD | NEW |