2 * Copyright (C) Research In Motion Limited 2010-2012. 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/SVGTextLayoutEngine.h"
23 #include "core/layout/svg/LayoutSVGInlineText.h"
24 #include "core/layout/svg/LayoutSVGTextPath.h"
25 #include "core/layout/svg/SVGTextChunkBuilder.h"
26 #include "core/layout/svg/SVGTextLayoutEngineBaseline.h"
27 #include "core/layout/svg/SVGTextLayoutEngineSpacing.h"
28 #include "core/layout/svg/line/SVGInlineFlowBox.h"
29 #include "core/layout/svg/line/SVGInlineTextBox.h"
30 #include "core/svg/SVGElement.h"
31 #include "core/svg/SVGLengthContext.h"
32 #include "core/svg/SVGTextContentElement.h"
36 SVGTextLayoutEngine::SVGTextLayoutEngine(Vector
<SVGTextLayoutAttributes
*>& layoutAttributes
)
37 : m_layoutAttributes(layoutAttributes
)
38 , m_layoutAttributesPosition(0)
39 , m_logicalCharacterOffset(0)
40 , m_logicalMetricsListOffset(0)
45 , m_isVerticalText(false)
46 , m_inPathLayout(false)
47 , m_textLengthSpacingInEffect(false)
48 , m_textPathCalculator(nullptr)
50 , m_textPathCurrentOffset(0)
51 , m_textPathSpacing(0)
52 , m_textPathScaling(1)
54 ASSERT(!m_layoutAttributes
.isEmpty());
57 void SVGTextLayoutEngine::updateCharacterPositionIfNeeded(float& x
, float& y
)
62 // Replace characters x/y position, with the current text position plus any
63 // relative adjustments, if it doesn't specify an absolute position itself.
64 if (SVGTextLayoutAttributes::isEmptyValue(x
))
67 if (SVGTextLayoutAttributes::isEmptyValue(y
))
74 void SVGTextLayoutEngine::updateCurrentTextPosition(float x
, float y
, float glyphAdvance
)
76 // Update current text position after processing the character.
77 if (m_isVerticalText
) {
79 m_y
= y
+ glyphAdvance
;
81 m_x
= x
+ glyphAdvance
;
86 void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(float dx
, float dy
)
88 // Update relative positioning information.
89 if (SVGTextLayoutAttributes::isEmptyValue(dx
) && SVGTextLayoutAttributes::isEmptyValue(dy
))
92 if (SVGTextLayoutAttributes::isEmptyValue(dx
))
94 if (SVGTextLayoutAttributes::isEmptyValue(dy
))
98 if (m_isVerticalText
) {
113 void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox
* textBox
)
115 ASSERT(!m_currentTextFragment
.length
);
117 // Figure out length of fragment.
118 m_currentTextFragment
.length
= m_visualMetricsIterator
.characterOffset() - m_currentTextFragment
.characterOffset
;
120 // Figure out fragment metrics.
121 const unsigned visualMetricsListOffset
= m_visualMetricsIterator
.metricsListOffset();
122 const Vector
<SVGTextMetrics
>& textMetricsValues
= m_visualMetricsIterator
.metricsList();
123 const SVGTextMetrics
& lastCharacterMetrics
= textMetricsValues
.at(visualMetricsListOffset
- 1);
124 m_currentTextFragment
.width
= lastCharacterMetrics
.width();
125 m_currentTextFragment
.height
= lastCharacterMetrics
.height();
127 if (m_currentTextFragment
.length
> 1) {
128 // SVGTextLayoutAttributesBuilder assures that the length of the range is equal to the sum of the individual lengths of the glyphs.
130 if (m_isVerticalText
) {
131 for (unsigned i
= m_currentTextFragment
.metricsListOffset
; i
< visualMetricsListOffset
; ++i
)
132 length
+= textMetricsValues
.at(i
).height();
133 m_currentTextFragment
.height
= length
;
135 for (unsigned i
= m_currentTextFragment
.metricsListOffset
; i
< visualMetricsListOffset
; ++i
)
136 length
+= textMetricsValues
.at(i
).width();
137 m_currentTextFragment
.width
= length
;
141 textBox
->textFragments().append(m_currentTextFragment
);
142 m_currentTextFragment
= SVGTextFragment();
145 void SVGTextLayoutEngine::beginTextPathLayout(SVGInlineFlowBox
* flowBox
)
147 // Build text chunks for all <textPath> children, using the line layout algorithm.
148 // This is needeed as text-anchor is just an additional startOffset for text paths.
149 SVGTextLayoutEngine
lineLayout(m_layoutAttributes
);
150 lineLayout
.m_textLengthSpacingInEffect
= m_textLengthSpacingInEffect
;
151 lineLayout
.layoutCharactersInTextBoxes(flowBox
);
153 m_inPathLayout
= true;
154 LayoutSVGTextPath
* textPath
= &toLayoutSVGTextPath(flowBox
->layoutObject());
156 Path path
= textPath
->layoutPath();
159 m_textPathCalculator
= new Path::PositionCalculator(path
);
160 m_textPathStartOffset
= textPath
->startOffset();
161 m_textPathLength
= path
.length();
162 if (m_textPathStartOffset
> 0 && m_textPathStartOffset
<= 1)
163 m_textPathStartOffset
*= m_textPathLength
;
165 SVGTextPathChunkBuilder textPathChunkLayoutBuilder
;
166 textPathChunkLayoutBuilder
.processTextChunks(lineLayout
.m_lineLayoutBoxes
);
168 m_textPathStartOffset
+= textPathChunkLayoutBuilder
.totalTextAnchorShift();
169 m_textPathCurrentOffset
= m_textPathStartOffset
;
171 // Eventually handle textLength adjustments.
172 SVGLengthAdjustType lengthAdjust
= SVGLengthAdjustUnknown
;
173 float desiredTextLength
= 0;
175 if (SVGTextContentElement
* textContentElement
= SVGTextContentElement::elementFromLayoutObject(textPath
)) {
176 SVGLengthContext
lengthContext(textContentElement
);
177 lengthAdjust
= textContentElement
->lengthAdjust()->currentValue()->enumValue();
178 if (textContentElement
->textLengthIsSpecifiedByUser())
179 desiredTextLength
= textContentElement
->textLength()->currentValue()->value(lengthContext
);
181 desiredTextLength
= 0;
184 if (!desiredTextLength
)
187 float totalLength
= textPathChunkLayoutBuilder
.totalLength();
188 if (lengthAdjust
== SVGLengthAdjustSpacing
)
189 m_textPathSpacing
= (desiredTextLength
- totalLength
) / textPathChunkLayoutBuilder
.totalCharacters();
191 m_textPathScaling
= desiredTextLength
/ totalLength
;
194 void SVGTextLayoutEngine::endTextPathLayout()
196 m_inPathLayout
= false;
197 delete m_textPathCalculator
;
198 m_textPathCalculator
= 0;
199 m_textPathLength
= 0;
200 m_textPathStartOffset
= 0;
201 m_textPathCurrentOffset
= 0;
202 m_textPathSpacing
= 0;
203 m_textPathScaling
= 1;
206 void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox
* textBox
)
210 LayoutSVGInlineText
& text
= toLayoutSVGInlineText(textBox
->layoutObject());
211 ASSERT(text
.parent());
212 ASSERT(text
.parent()->node());
213 ASSERT(text
.parent()->node()->isSVGElement());
215 const ComputedStyle
& style
= text
.styleRef();
217 textBox
->clearTextFragments();
218 m_isVerticalText
= style
.svgStyle().isVerticalWritingMode();
219 layoutTextOnLineOrPath(textBox
, text
, style
);
224 m_lineLayoutBoxes
.append(textBox
);
227 static bool definesTextLengthWithSpacing(const InlineFlowBox
* start
)
229 SVGTextContentElement
* textContentElement
= SVGTextContentElement::elementFromLayoutObject(&start
->layoutObject());
230 return textContentElement
231 && textContentElement
->lengthAdjust()->currentValue()->enumValue() == SVGLengthAdjustSpacing
232 && textContentElement
->textLengthIsSpecifiedByUser();
235 void SVGTextLayoutEngine::layoutCharactersInTextBoxes(InlineFlowBox
* start
)
237 bool textLengthSpacingInEffect
= m_textLengthSpacingInEffect
|| definesTextLengthWithSpacing(start
);
238 TemporaryChange
<bool> textLengthSpacingScope(m_textLengthSpacingInEffect
, textLengthSpacingInEffect
);
240 for (InlineBox
* child
= start
->firstChild(); child
; child
= child
->nextOnLine()) {
241 if (child
->isSVGInlineTextBox()) {
242 ASSERT(child
->layoutObject().isSVGInlineText());
243 layoutInlineTextBox(toSVGInlineTextBox(child
));
245 // Skip generated content.
246 Node
* node
= child
->layoutObject().node();
250 SVGInlineFlowBox
* flowBox
= toSVGInlineFlowBox(child
);
251 bool isTextPath
= isSVGTextPathElement(*node
);
253 beginTextPathLayout(flowBox
);
255 layoutCharactersInTextBoxes(flowBox
);
263 void SVGTextLayoutEngine::finishLayout()
265 m_visualMetricsIterator
= SVGInlineTextMetricsIterator();
267 // After all text fragments are stored in their correpsonding SVGInlineTextBoxes, we can layout individual text chunks.
268 // Chunk layouting is only performed for line layout boxes, not for path layout, where it has already been done.
269 SVGTextChunkBuilder chunkLayoutBuilder
;
270 chunkLayoutBuilder
.processTextChunks(m_lineLayoutBoxes
);
272 m_lineLayoutBoxes
.clear();
275 bool SVGTextLayoutEngine::currentLogicalCharacterAttributes(SVGTextLayoutAttributes
*& logicalAttributes
)
277 if (m_layoutAttributesPosition
== m_layoutAttributes
.size())
280 logicalAttributes
= m_layoutAttributes
[m_layoutAttributesPosition
];
281 ASSERT(logicalAttributes
);
283 if (m_logicalCharacterOffset
!= logicalAttributes
->context()->textLength())
286 ++m_layoutAttributesPosition
;
287 if (m_layoutAttributesPosition
== m_layoutAttributes
.size())
290 logicalAttributes
= m_layoutAttributes
[m_layoutAttributesPosition
];
291 m_logicalMetricsListOffset
= 0;
292 m_logicalCharacterOffset
= 0;
296 bool SVGTextLayoutEngine::currentLogicalCharacterMetrics(SVGTextLayoutAttributes
*& logicalAttributes
, SVGTextMetrics
& logicalMetrics
)
298 const Vector
<SVGTextMetrics
>* textMetricsValues
= &logicalAttributes
->textMetricsValues();
299 unsigned textMetricsSize
= textMetricsValues
->size();
301 if (m_logicalMetricsListOffset
== textMetricsSize
) {
302 if (!currentLogicalCharacterAttributes(logicalAttributes
))
305 textMetricsValues
= &logicalAttributes
->textMetricsValues();
306 textMetricsSize
= textMetricsValues
->size();
310 ASSERT(textMetricsSize
);
311 ASSERT(m_logicalMetricsListOffset
< textMetricsSize
);
312 logicalMetrics
= textMetricsValues
->at(m_logicalMetricsListOffset
);
313 if (logicalMetrics
.isEmpty() || (!logicalMetrics
.width() && !logicalMetrics
.height())) {
314 advanceToNextLogicalCharacter(logicalMetrics
);
318 // Stop if we found the next valid logical text metrics object.
322 ASSERT_NOT_REACHED();
326 void SVGTextLayoutEngine::advanceToNextLogicalCharacter(const SVGTextMetrics
& logicalMetrics
)
328 ++m_logicalMetricsListOffset
;
329 m_logicalCharacterOffset
+= logicalMetrics
.length();
332 void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox
* textBox
, const LayoutSVGInlineText
& text
, const ComputedStyle
& style
)
334 if (m_inPathLayout
&& !m_textPathCalculator
)
337 const SVGComputedStyle
& svgStyle
= style
.svgStyle();
339 // Find the start of the current text box in the metrics list.
340 m_visualMetricsIterator
.advanceToTextStart(&text
, textBox
->start());
342 const Font
& font
= style
.font();
344 SVGTextLayoutEngineSpacing
spacingLayout(font
, style
.effectiveZoom());
345 SVGTextLayoutEngineBaseline
baselineLayout(font
, style
.effectiveZoom());
347 bool didStartTextFragment
= false;
348 bool applySpacingToNextCharacter
= false;
351 float baselineShift
= baselineLayout
.calculateBaselineShift(style
);
352 baselineShift
-= baselineLayout
.calculateAlignmentBaselineShift(m_isVerticalText
, &text
);
354 // Main layout algorithm.
355 const unsigned boxEndOffset
= textBox
->start() + textBox
->len();
356 while (!m_visualMetricsIterator
.isAtEnd() && m_visualMetricsIterator
.characterOffset() < boxEndOffset
) {
357 const SVGTextMetrics
& visualMetrics
= m_visualMetricsIterator
.metrics();
358 if (visualMetrics
.isEmpty()) {
359 m_visualMetricsIterator
.next();
363 SVGTextLayoutAttributes
* logicalAttributes
= nullptr;
364 if (!currentLogicalCharacterAttributes(logicalAttributes
))
367 ASSERT(logicalAttributes
);
368 SVGTextMetrics
logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics
);
369 if (!currentLogicalCharacterMetrics(logicalAttributes
, logicalMetrics
))
372 SVGCharacterDataMap
& characterDataMap
= logicalAttributes
->characterDataMap();
373 SVGCharacterData data
;
374 SVGCharacterDataMap::iterator it
= characterDataMap
.find(m_logicalCharacterOffset
+ 1);
375 if (it
!= characterDataMap
.end())
381 // When we've advanced to the box start offset, determine using the original x/y values,
382 // whether this character starts a new text chunk, before doing any further processing.
383 if (m_visualMetricsIterator
.characterOffset() == textBox
->start())
384 textBox
->setStartsNewTextChunk(logicalAttributes
->context()->characterStartsNewTextChunk(m_logicalCharacterOffset
));
386 float angle
= SVGTextLayoutAttributes::isEmptyValue(data
.rotate
) ? 0 : data
.rotate
;
388 // Calculate glyph orientation angle.
389 UChar currentCharacter
= text
.characterAt(m_visualMetricsIterator
.characterOffset());
390 float orientationAngle
= baselineLayout
.calculateGlyphOrientationAngle(m_isVerticalText
, svgStyle
, currentCharacter
);
392 // Calculate glyph advance & x/y orientation shifts.
393 float xOrientationShift
= 0;
394 float yOrientationShift
= 0;
395 float glyphAdvance
= baselineLayout
.calculateGlyphAdvanceAndOrientation(m_isVerticalText
, visualMetrics
, orientationAngle
, xOrientationShift
, yOrientationShift
);
397 // Assign current text position to x/y values, if needed.
398 updateCharacterPositionIfNeeded(x
, y
);
400 // Apply dx/dy value adjustments to current text position, if needed.
401 updateRelativePositionAdjustmentsIfNeeded(data
.dx
, data
.dy
);
403 // Calculate CSS 'letter-spacing' and 'word-spacing' for next character, if needed.
404 float spacing
= spacingLayout
.calculateCSSSpacing(currentCharacter
);
406 float textPathOffset
= 0;
407 if (m_inPathLayout
) {
408 float scaledGlyphAdvance
= glyphAdvance
* m_textPathScaling
;
409 if (m_isVerticalText
) {
410 // If there's an absolute y position available, it marks the beginning of a new position along the path.
411 if (!SVGTextLayoutAttributes::isEmptyValue(y
))
412 m_textPathCurrentOffset
= y
+ m_textPathStartOffset
;
414 m_textPathCurrentOffset
+= m_dy
;
417 // Apply dx/dy correction and setup translations that move to the glyph midpoint.
418 xOrientationShift
+= m_dx
+ baselineShift
;
419 yOrientationShift
-= scaledGlyphAdvance
/ 2;
421 // If there's an absolute x position available, it marks the beginning of a new position along the path.
422 if (!SVGTextLayoutAttributes::isEmptyValue(x
))
423 m_textPathCurrentOffset
= x
+ m_textPathStartOffset
;
425 m_textPathCurrentOffset
+= m_dx
;
428 // Apply dx/dy correction and setup translations that move to the glyph midpoint.
429 xOrientationShift
-= scaledGlyphAdvance
/ 2;
430 yOrientationShift
+= m_dy
- baselineShift
;
433 // Calculate current offset along path.
434 textPathOffset
= m_textPathCurrentOffset
+ scaledGlyphAdvance
/ 2;
436 // Move to next character.
437 m_textPathCurrentOffset
+= scaledGlyphAdvance
+ m_textPathSpacing
+ spacing
* m_textPathScaling
;
439 // Skip character, if we're before the path.
440 if (textPathOffset
< 0) {
441 advanceToNextLogicalCharacter(logicalMetrics
);
442 m_visualMetricsIterator
.next();
446 // Stop processing, if the next character lies behind the path.
447 if (textPathOffset
> m_textPathLength
)
451 bool ok
= m_textPathCalculator
->pointAndNormalAtLength(textPathOffset
, point
, angle
);
452 ASSERT_UNUSED(ok
, ok
);
456 // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
457 if (m_isVerticalText
)
460 // Apply all previously calculated shift values.
461 if (m_isVerticalText
)
470 // Determine whether we have to start a new fragment.
471 bool shouldStartNewFragment
= m_dx
|| m_dy
|| m_isVerticalText
|| m_inPathLayout
|| angle
|| angle
!= lastAngle
472 || orientationAngle
|| applySpacingToNextCharacter
|| m_textLengthSpacingInEffect
;
474 // If we already started a fragment, close it now.
475 if (didStartTextFragment
&& shouldStartNewFragment
) {
476 applySpacingToNextCharacter
= false;
477 recordTextFragment(textBox
);
480 // Eventually start a new fragment, if not yet done.
481 if (!didStartTextFragment
|| shouldStartNewFragment
) {
482 ASSERT(!m_currentTextFragment
.characterOffset
);
483 ASSERT(!m_currentTextFragment
.length
);
485 didStartTextFragment
= true;
486 m_currentTextFragment
.characterOffset
= m_visualMetricsIterator
.characterOffset();
487 m_currentTextFragment
.metricsListOffset
= m_visualMetricsIterator
.metricsListOffset();
488 m_currentTextFragment
.x
= x
;
489 m_currentTextFragment
.y
= y
;
491 // Build fragment transformation.
493 m_currentTextFragment
.transform
.rotate(angle
);
495 if (xOrientationShift
|| yOrientationShift
)
496 m_currentTextFragment
.transform
.translate(xOrientationShift
, yOrientationShift
);
498 if (orientationAngle
)
499 m_currentTextFragment
.transform
.rotate(orientationAngle
);
501 m_currentTextFragment
.isTextOnPath
= m_inPathLayout
&& m_textPathScaling
!= 1;
502 if (m_currentTextFragment
.isTextOnPath
) {
503 if (m_isVerticalText
)
504 m_currentTextFragment
.lengthAdjustTransform
.scaleNonUniform(1, m_textPathScaling
);
506 m_currentTextFragment
.lengthAdjustTransform
.scaleNonUniform(m_textPathScaling
, 1);
510 // Update current text position, after processing of the current character finished.
511 if (m_inPathLayout
) {
512 updateCurrentTextPosition(x
, y
, glyphAdvance
);
514 // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
516 applySpacingToNextCharacter
= true;
518 float xNew
= x
- m_dx
;
519 float yNew
= y
- m_dy
;
521 if (m_isVerticalText
)
522 xNew
-= baselineShift
;
524 yNew
+= baselineShift
;
526 updateCurrentTextPosition(xNew
, yNew
, glyphAdvance
+ spacing
);
529 advanceToNextLogicalCharacter(logicalMetrics
);
530 m_visualMetricsIterator
.next();
534 if (!didStartTextFragment
)
537 // Close last open fragment, if needed.
538 recordTextFragment(textBox
);