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 |