2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
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.
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.
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.
21 #include "core/layout/svg/SVGTextChunkBuilder.h"
23 #include "core/layout/svg/LayoutSVGInlineText.h"
24 #include "core/layout/svg/line/SVGInlineTextBox.h"
25 #include "core/svg/SVGLengthContext.h"
26 #include "core/svg/SVGTextContentElement.h"
27 #include "platform/transforms/AffineTransform.h"
33 float calculateTextAnchorShift(const ComputedStyle
& style
, float length
)
35 bool isLTR
= style
.isLeftToRightDirection();
36 switch (style
.svgStyle().textAnchor()) {
40 return isLTR
? 0 : -length
;
44 return isLTR
? -length
: 0;
48 bool needsTextAnchorAdjustment(const ComputedStyle
& style
)
50 bool isLTR
= style
.isLeftToRightDirection();
51 switch (style
.svgStyle().textAnchor()) {
63 class ChunkLengthAccumulator
{
65 ChunkLengthAccumulator(bool isVertical
)
68 , m_isVertical(isVertical
)
72 typedef Vector
<SVGInlineTextBox
*>::const_iterator BoxListConstIterator
;
74 void processRange(BoxListConstIterator boxStart
, BoxListConstIterator boxEnd
);
81 float length() const { return m_length
; }
82 unsigned numCharacters() const { return m_numCharacters
; }
85 unsigned m_numCharacters
;
87 const bool m_isVertical
;
90 void ChunkLengthAccumulator::processRange(BoxListConstIterator boxStart
, BoxListConstIterator boxEnd
)
92 SVGTextFragment
* lastFragment
= nullptr;
93 for (auto boxIter
= boxStart
; boxIter
!= boxEnd
; ++boxIter
) {
94 for (SVGTextFragment
& fragment
: (*boxIter
)->textFragments()) {
95 m_numCharacters
+= fragment
.length
;
98 m_length
+= fragment
.height
;
100 m_length
+= fragment
.width
;
103 lastFragment
= &fragment
;
107 // Respect gap between chunks.
109 m_length
+= fragment
.y
- (lastFragment
->y
+ lastFragment
->height
);
111 m_length
+= fragment
.x
- (lastFragment
->x
+ lastFragment
->width
);
113 lastFragment
= &fragment
;
120 SVGTextChunkBuilder::SVGTextChunkBuilder()
124 void SVGTextChunkBuilder::processTextChunks(const Vector
<SVGInlineTextBox
*>& lineLayoutBoxes
)
126 if (lineLayoutBoxes
.isEmpty())
129 bool foundStart
= false;
130 auto boxIter
= lineLayoutBoxes
.begin();
131 auto endBox
= lineLayoutBoxes
.end();
132 auto chunkStartBox
= boxIter
;
133 for (; boxIter
!= endBox
; ++boxIter
) {
134 if (!(*boxIter
)->startsNewTextChunk())
140 ASSERT(boxIter
!= chunkStartBox
);
141 handleTextChunk(chunkStartBox
, boxIter
);
143 chunkStartBox
= boxIter
;
149 if (boxIter
!= chunkStartBox
)
150 handleTextChunk(chunkStartBox
, boxIter
);
153 SVGTextPathChunkBuilder::SVGTextPathChunkBuilder()
154 : SVGTextChunkBuilder()
156 , m_totalCharacters(0)
157 , m_totalTextAnchorShift(0)
161 void SVGTextPathChunkBuilder::handleTextChunk(BoxListConstIterator boxStart
, BoxListConstIterator boxEnd
)
163 const ComputedStyle
& style
= (*boxStart
)->layoutObject().styleRef();
165 ChunkLengthAccumulator
lengthAccumulator(style
.svgStyle().isVerticalWritingMode());
166 lengthAccumulator
.processRange(boxStart
, boxEnd
);
168 // Handle text-anchor as additional start offset for text paths.
169 m_totalTextAnchorShift
+= calculateTextAnchorShift(style
, lengthAccumulator
.length());
171 m_totalLength
+= lengthAccumulator
.length();
172 m_totalCharacters
+= lengthAccumulator
.numCharacters();
175 static void buildSpacingAndGlyphsTransform(bool isVerticalText
, float scale
, const SVGTextFragment
& fragment
, AffineTransform
& spacingAndGlyphsTransform
)
177 spacingAndGlyphsTransform
.translate(fragment
.x
, fragment
.y
);
180 spacingAndGlyphsTransform
.scaleNonUniform(1, scale
);
182 spacingAndGlyphsTransform
.scaleNonUniform(scale
, 1);
184 spacingAndGlyphsTransform
.translate(-fragment
.x
, -fragment
.y
);
187 void SVGTextChunkBuilder::handleTextChunk(BoxListConstIterator boxStart
, BoxListConstIterator boxEnd
)
191 const LayoutSVGInlineText
& textLayoutObject
= toLayoutSVGInlineText((*boxStart
)->layoutObject());
192 const ComputedStyle
& style
= textLayoutObject
.styleRef();
194 // Handle 'lengthAdjust' property.
195 float desiredTextLength
= 0;
196 SVGLengthAdjustType lengthAdjust
= SVGLengthAdjustUnknown
;
197 if (SVGTextContentElement
* textContentElement
= SVGTextContentElement::elementFromLayoutObject(textLayoutObject
.parent())) {
198 lengthAdjust
= textContentElement
->lengthAdjust()->currentValue()->enumValue();
200 SVGLengthContext
lengthContext(textContentElement
);
201 if (textContentElement
->textLengthIsSpecifiedByUser())
202 desiredTextLength
= textContentElement
->textLength()->currentValue()->value(lengthContext
);
204 desiredTextLength
= 0;
207 bool processTextLength
= desiredTextLength
> 0;
208 bool processTextAnchor
= needsTextAnchorAdjustment(style
);
209 if (!processTextAnchor
&& !processTextLength
)
212 bool isVerticalText
= style
.svgStyle().isVerticalWritingMode();
214 // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
215 ChunkLengthAccumulator
lengthAccumulator(isVerticalText
);
216 lengthAccumulator
.processRange(boxStart
, boxEnd
);
218 if (processTextLength
) {
219 float chunkLength
= lengthAccumulator
.length();
220 if (lengthAdjust
== SVGLengthAdjustSpacing
) {
221 float textLengthShift
= (desiredTextLength
- chunkLength
) / lengthAccumulator
.numCharacters();
222 unsigned atCharacter
= 0;
223 for (auto boxIter
= boxStart
; boxIter
!= boxEnd
; ++boxIter
) {
224 Vector
<SVGTextFragment
>& fragments
= (*boxIter
)->textFragments();
225 if (fragments
.isEmpty())
227 processTextLengthSpacingCorrection(isVerticalText
, textLengthShift
, fragments
, atCharacter
);
230 // Fragments have been adjusted, we have to recalculate the chunk
231 // length, to be able to apply the text-anchor shift.
232 if (processTextAnchor
) {
233 lengthAccumulator
.reset();
234 lengthAccumulator
.processRange(boxStart
, boxEnd
);
237 ASSERT(lengthAdjust
== SVGLengthAdjustSpacingAndGlyphs
);
238 float textLengthScale
= desiredTextLength
/ chunkLength
;
239 AffineTransform spacingAndGlyphsTransform
;
241 bool foundFirstFragment
= false;
242 for (auto boxIter
= boxStart
; boxIter
!= boxEnd
; ++boxIter
) {
243 SVGInlineTextBox
* textBox
= *boxIter
;
244 Vector
<SVGTextFragment
>& fragments
= textBox
->textFragments();
245 if (fragments
.isEmpty())
248 if (!foundFirstFragment
) {
249 foundFirstFragment
= true;
250 buildSpacingAndGlyphsTransform(isVerticalText
, textLengthScale
, fragments
.first(), spacingAndGlyphsTransform
);
253 applyTextLengthScaleAdjustment(spacingAndGlyphsTransform
, fragments
);
258 if (!processTextAnchor
)
261 float textAnchorShift
= calculateTextAnchorShift(style
, lengthAccumulator
.length());
262 for (auto boxIter
= boxStart
; boxIter
!= boxEnd
; ++boxIter
) {
263 Vector
<SVGTextFragment
>& fragments
= (*boxIter
)->textFragments();
264 if (fragments
.isEmpty())
266 processTextAnchorCorrection(isVerticalText
, textAnchorShift
, fragments
);
270 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText
, float textLengthShift
, Vector
<SVGTextFragment
>& fragments
, unsigned& atCharacter
)
272 for (SVGTextFragment
& fragment
: fragments
) {
274 fragment
.y
+= textLengthShift
* atCharacter
;
276 fragment
.x
+= textLengthShift
* atCharacter
;
278 atCharacter
+= fragment
.length
;
282 void SVGTextChunkBuilder::applyTextLengthScaleAdjustment(const AffineTransform
& spacingAndGlyphsTransform
, Vector
<SVGTextFragment
>& fragments
)
284 for (SVGTextFragment
& fragment
: fragments
) {
285 ASSERT(fragment
.lengthAdjustTransform
.isIdentity());
286 fragment
.lengthAdjustTransform
= spacingAndGlyphsTransform
;
290 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText
, float textAnchorShift
, Vector
<SVGTextFragment
>& fragments
)
292 for (SVGTextFragment
& fragment
: fragments
) {
294 fragment
.y
+= textAnchorShift
;
296 fragment
.x
+= textAnchorShift
;