2 * This file is part of the WebKit project.
4 * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
5 * (C) 2006 Apple Computer Inc.
6 * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
26 #include "wtf/Platform.h"
29 #include "SVGRootInlineBox.h"
33 #include "GraphicsContext.h"*/
34 #include "RenderSVGRoot.h"
35 #include "SVGInlineFlowBox.h"
36 #include "SVGInlineTextBox.h"
37 #include "SVGFontElement.h"
38 #include "SVGPaintServer.h"
39 #include "SVGRenderStyleDefs.h"
40 #include "SVGRenderSupport.h"
41 #include "SVGResourceFilter.h"
42 #include "SVGTextPositioningElement.h"
43 #include "SVGURIReference.h"
45 //#include "UnicodeRange.h"
50 // Text chunk creation is complex and the whole process
51 // can easily be traced by setting this variable > 0.
52 #define DEBUG_CHUNK_BUILDING 0
56 static inline bool isVerticalWritingMode(const SVGRenderStyle
* style
)
58 return style
->writingMode() == WM_TBRL
|| style
->writingMode() == WM_TB
;
61 static inline EAlignmentBaseline
dominantBaselineToShift(bool isVerticalText
, const RenderObject
* text
, const Font
& font
)
65 const SVGRenderStyle
* style
= text
->style() ? text
->style()->svgStyle() : 0;
68 const SVGRenderStyle
* parentStyle
= text
->parent() && text
->parent()->style() ? text
->parent()->style()->svgStyle() : 0;
70 EDominantBaseline baseline
= style
->dominantBaseline();
71 if (baseline
== DB_AUTO
) {
73 baseline
= DB_CENTRAL
;
75 baseline
= DB_ALPHABETIC
;
80 // TODO: The dominant-baseline and the baseline-table components are set by
81 // determining the predominant script of the character data content.
86 return dominantBaselineToShift(isVerticalText
, text
->parent(), font
);
94 return dominantBaselineToShift(isVerticalText
, text
->parent(), font
);
100 return AB_IDEOGRAPHIC
;
102 return AB_ALPHABETIC
;
105 case DB_MATHEMATICAL
:
106 return AB_MATHEMATICAL
;
111 case DB_TEXT_AFTER_EDGE
:
112 return AB_TEXT_AFTER_EDGE
;
113 case DB_TEXT_BEFORE_EDGE
:
114 return AB_TEXT_BEFORE_EDGE
;
116 ASSERT_NOT_REACHED();
121 static inline float alignmentBaselineToShift(bool isVerticalText
, const RenderObject
* text
, const Font
& font
)
125 const SVGRenderStyle
* style
= text
->style() ? text
->style()->svgStyle() : 0;
128 const SVGRenderStyle
* parentStyle
= text
->parent() && text
->parent()->style() ? text
->parent()->style()->svgStyle() : 0;
130 EAlignmentBaseline baseline
= style
->alignmentBaseline();
131 if (baseline
== AB_AUTO
) {
132 if (parentStyle
&& style
->dominantBaseline() == DB_AUTO
)
133 baseline
= dominantBaselineToShift(isVerticalText
, text
->parent(), font
);
135 baseline
= dominantBaselineToShift(isVerticalText
, text
, font
);
137 ASSERT(baseline
!= AB_AUTO
);
140 // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
145 return dominantBaselineToShift(isVerticalText
, text
->parent(), font
);
150 case AB_TEXT_BEFORE_EDGE
:
151 return font
.ascent();
153 return font
.xHeight() / 2.0f
;
155 // Not needed, we're taking this into account already for vertical text!
156 // return (font.ascent() - font.descent()) / 2.0f;
159 case AB_TEXT_AFTER_EDGE
:
161 return font
.descent();
165 return font
.ascent() * 8.0f
/ 10.0f
;
166 case AB_MATHEMATICAL
:
167 return font
.ascent() / 2.0f
;
169 ASSERT_NOT_REACHED();
174 static inline float glyphOrientationToAngle(const SVGRenderStyle
* svgStyle
, bool isVerticalText
, const UChar
& character
)
176 switch (isVerticalText
? svgStyle
->glyphOrientationVertical() : svgStyle
->glyphOrientationHorizontal()) {
179 // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
180 // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
181 /*FIXME: khtml porting; unsigned int unicodeRange = findCharUnicodeRange(character);
182 if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
199 static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle
)
201 return fabsf(fmodf(orientationAngle
, 180.0f
)) == 0.0f
;
204 static inline float calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText
, float orientationAngle
, float glyphWidth
, float glyphHeight
, const Font
& font
, SVGChar
& svgChar
, float& xOrientationShift
, float& yOrientationShift
)
206 bool orientationIsMultiplyOf180Degrees
= glyphOrientationIsMultiplyOf180Degrees(orientationAngle
);
208 // The function is based on spec requirements:
210 // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
211 // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
213 // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
214 // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph.
216 // vertical orientation handling
217 if (isVerticalText
) {
218 if (orientationAngle
== 0.0f
) {
219 xOrientationShift
= -glyphWidth
/ 2.0f
;
220 yOrientationShift
= font
.ascent();
221 } else if (orientationAngle
== 90.0f
) {
222 xOrientationShift
= -glyphHeight
;
223 yOrientationShift
= font
.descent();
224 svgChar
.orientationShiftY
= -font
.ascent();
225 } else if (orientationAngle
== 270.0f
) {
226 xOrientationShift
= glyphHeight
;
227 yOrientationShift
= font
.descent();
228 svgChar
.orientationShiftX
= -glyphWidth
;
229 svgChar
.orientationShiftY
= -font
.ascent();
230 } else if (orientationAngle
== 180.0f
) {
231 yOrientationShift
= font
.ascent();
232 svgChar
.orientationShiftX
= -glyphWidth
/ 2.0f
;
233 svgChar
.orientationShiftY
= font
.ascent() - font
.descent();
236 // vertical advance calculation
237 if (orientationAngle
!= 0.0f
&& !orientationIsMultiplyOf180Degrees
)
243 // horizontal orientation handling
244 if (orientationAngle
== 90.0f
) {
245 xOrientationShift
= glyphWidth
/ 2.0f
;
246 yOrientationShift
= -font
.descent();
247 svgChar
.orientationShiftX
= -glyphWidth
/ 2.0f
- font
.descent();
248 svgChar
.orientationShiftY
= font
.descent();
249 } else if (orientationAngle
== 270.0f
) {
250 xOrientationShift
= -glyphWidth
/ 2.0f
;
251 yOrientationShift
= -font
.descent();
252 svgChar
.orientationShiftX
= -glyphWidth
/ 2.0f
+ font
.descent();
253 svgChar
.orientationShiftY
= glyphHeight
;
254 } else if (orientationAngle
== 180.0f
) {
255 xOrientationShift
= glyphWidth
/ 2.0f
;
256 svgChar
.orientationShiftX
= -glyphWidth
/ 2.0f
;
257 svgChar
.orientationShiftY
= font
.ascent() - font
.descent();
260 // horizontal advance calculation
261 if (orientationAngle
!= 0.0f
&& !orientationIsMultiplyOf180Degrees
)
267 static inline void startTextChunk(SVGTextChunkLayoutInfo
& info
)
269 info
.chunk
.boxes
.clear();
270 info
.chunk
.boxes
.append(SVGInlineBoxCharacterRange());
272 info
.chunk
.start
= info
.it
;
273 info
.assignChunkProperties
= true;
276 static inline void closeTextChunk(SVGTextChunkLayoutInfo
& info
)
278 ASSERT(!info
.chunk
.boxes
.last().isOpen());
279 ASSERT(info
.chunk
.boxes
.last().isClosed());
281 info
.chunk
.end
= info
.it
;
282 ASSERT(info
.chunk
.end
>= info
.chunk
.start
);
284 info
.svgTextChunks
.append(info
.chunk
);
287 RenderSVGRoot
* findSVGRootObject(RenderObject
* start
)
289 // Find associated root inline box
290 while (start
&& !start
->isSVGRoot())
291 start
= start
->parent();
294 ASSERT(start
->isSVGRoot());
296 return static_cast<RenderSVGRoot
*>(start
);
299 static inline FloatPoint
topLeftPositionOfCharacterRange(Vector
<SVGChar
>& chars
)
301 return topLeftPositionOfCharacterRange(chars
.begin(), chars
.end());
304 FloatPoint
topLeftPositionOfCharacterRange(Vector
<SVGChar
>::iterator it
, Vector
<SVGChar
>::iterator end
)
306 float lowX
= FLT_MAX
, lowY
= FLT_MAX
;
307 for (; it
!= end
; ++it
) {
321 return FloatPoint(lowX
, lowY
);
325 static float calculateKerning(RenderObject
* item
)
327 /*FIXME const Font& font = item->style()->font();
328 const SVGRenderStyle* svgStyle = item->style()->svgStyle();
330 float kerning = 0.0f;
331 if (CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->kerning())) {
332 kerning = primitive->getFloatValue();
334 if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize() > 0)
335 kerning = kerning / 100.0f * font.pixelSize();
342 // Helper class for paint()
343 struct SVGRootInlineBoxPaintWalker
{
344 SVGRootInlineBoxPaintWalker(SVGRootInlineBox
* rootBox
, SVGResourceFilter
* rootFilter
, RenderObject::PaintInfo paintInfo
, int tx
, int ty
)
346 , m_chunkStarted(false)
347 , m_paintInfo(paintInfo
)
348 , m_savedInfo(paintInfo
)
349 , m_boundingBox(tx
+ rootBox
->xPos(), ty
+ rootBox
->yPos(), rootBox
->width(), rootBox
->height())
351 , m_rootFilter(rootFilter
)
352 , m_fillPaintServer(0)
353 , m_strokePaintServer(0)
354 , m_fillPaintServerObject(0)
355 , m_strokePaintServerObject(0)
361 ~SVGRootInlineBoxPaintWalker()
364 ASSERT(!m_fillPaintServer
);
365 ASSERT(!m_fillPaintServerObject
);
366 ASSERT(!m_strokePaintServer
);
367 ASSERT(!m_strokePaintServerObject
);
368 ASSERT(!m_chunkStarted
);
371 void teardownFillPaintServer()
373 if (!m_fillPaintServer
)
376 m_fillPaintServer
->teardown(m_paintInfo
.p
, 0, m_fillPaintServerObject
, ApplyToFillTargetType
, true);
378 m_fillPaintServer
= 0;
379 m_fillPaintServerObject
= 0;
382 void teardownStrokePaintServer()
384 if (!m_strokePaintServer
)
387 m_strokePaintServer
->teardown(m_paintInfo
.p
, 0, m_strokePaintServerObject
, ApplyToStrokeTargetType
, true);
389 m_strokePaintServer
= 0;
390 m_strokePaintServerObject
= 0;
393 void chunkStartCallback(InlineBox
* box
)
395 ASSERT(!m_chunkStarted
);
396 m_chunkStarted
= true;
398 InlineFlowBox
* flowBox
= box
->parent();
400 // Initialize text rendering
401 RenderObject
* object
= flowBox
->object();
404 m_savedInfo
= m_paintInfo
;
405 //m_paintInfo.context->save();
407 if (!flowBox
->isRootInlineBox())
408 ;//FIXME m_paintInfo.context->concatCTM(m_rootBox->object()->localTransform());
410 //m_paintInfo.context->concatCTM(object->localTransform());
411 m_paintInfo
.p
->setWorldMatrix(object
->localTransform(), true);
413 if (!flowBox
->isRootInlineBox()) {
414 prepareToRenderSVGContent(object
, m_paintInfo
, m_boundingBox
, m_filter
, m_rootFilter
);
415 // FIXME khtml m_paintInfo.rect = object->localTransform().inverse().mapRect(m_paintInfo.rect);
419 void chunkEndCallback(InlineBox
* box
)
421 ASSERT(m_chunkStarted
);
422 m_chunkStarted
= false;
424 InlineFlowBox
* flowBox
= box
->parent();
426 RenderObject
* object
= flowBox
->object();
429 // Clean up last used paint server
430 teardownFillPaintServer();
431 teardownStrokePaintServer();
433 // Finalize text rendering
434 if (!flowBox
->isRootInlineBox()) {
435 //FIXME khtml finishRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_savedInfo.context);
439 // Restore context & repaint rect
440 //FIXME m_paintInfo.context->restore();
441 //FIXME m_paintInfo.rect = m_savedInfo.rect;
444 bool chunkSetupFillCallback(InlineBox
* box
)
446 InlineFlowBox
* flowBox
= box
->parent();
448 // Setup fill paint server
449 RenderObject
* object
= flowBox
->object();
452 ASSERT(!m_strokePaintServer
);
453 teardownFillPaintServer();
455 m_fillPaintServer
= SVGPaintServer::fillPaintServer(object
->style(), object
);
456 if (m_fillPaintServer
) {
457 m_fillPaintServer
->setup(m_paintInfo
.p
, 0, object
, ApplyToFillTargetType
, true);
458 m_fillPaintServerObject
= object
;
465 bool chunkSetupStrokeCallback(InlineBox
* box
)
467 InlineFlowBox
* flowBox
= box
->parent();
469 // Setup stroke paint server
470 RenderObject
* object
= flowBox
->object();
473 // If we're both stroked & filled, teardown fill paint server before stroking.
474 teardownFillPaintServer();
475 teardownStrokePaintServer();
477 m_strokePaintServer
= SVGPaintServer::strokePaintServer(object
->style(), object
);
479 if (m_strokePaintServer
) {
480 m_strokePaintServer
->setup(m_paintInfo
.p
, 0, object
, ApplyToStrokeTargetType
, true);
481 m_strokePaintServerObject
= object
;
488 void chunkPortionCallback(SVGInlineTextBox
* textBox
, int startOffset
, const AffineTransform
& chunkCtm
,
489 const Vector
<SVGChar
>::iterator
& start
, const Vector
<SVGChar
>::iterator
& end
)
491 //kDebug() << "text chunk rendering code here" << endl;
492 RenderText
* text
= textBox
->/*textObject()*/renderText();
495 RenderStyle
* styleToUse
= text
->style(/*textBox->isFirstLineStyle()*/);
498 startOffset
+= textBox
->start();
500 int textDecorations
= styleToUse
->textDecorationsInEffect();
503 IntPoint decorationOrigin
;
504 SVGTextDecorationInfo info
;
506 /*FIXME khtml if (!chunkCtm.isIdentity())
507 m_paintInfo.context->concatCTM(chunkCtm);*/
509 for (Vector
<SVGChar
>::iterator it
= start
; it
!= end
; ++it
) {
513 // Determine how many characters - starting from the current - can be drawn at once.
514 Vector
<SVGChar
>::iterator itSearch
= it
+ 1;
515 while (itSearch
!= end
) {
516 if (itSearch
->drawnSeperated
|| itSearch
->isHidden())
522 /*FIXME khtmlconst*/ UChar
* stringStart
= text
->text() + startOffset
+ (it
- start
);
523 unsigned int stringLength
= itSearch
- it
;
525 // Paint decorations, that have to be drawn before the text gets drawn
526 if (textDecorations
!= TDNONE
/*FIXME khtml && m_paintInfo.phase != PaintPhaseSelection*/) {
527 //khtml textWidth = styleToUse->font().width(svgTextRunForInlineTextBox(stringStart, stringLength, styleToUse, textBox, (*it).x));
528 textWidth
= styleToUse
->htmlFont().width(stringStart
, stringLength
, 0, stringLength
, false /*fast algo*/);
529 decorationOrigin
= IntPoint((int) (*it
).x
, (int) (*it
).y
- styleToUse
->htmlFont().ascent());
530 info
= m_rootBox
->retrievePaintServersForTextDecoration(text
);
533 /*if (textDecorations & UNDERLINE && textWidth != 0.0f)
534 textBox->paintDecoration(UNDERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);
536 if (textDecorations & OVERLINE && textWidth != 0.0f)
537 textBox->paintDecoration(OVERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);*/
540 SVGPaintServer
* activePaintServer
= m_fillPaintServer
;
541 if (!activePaintServer
)
542 activePaintServer
= m_strokePaintServer
;
544 ASSERT(activePaintServer
);
545 textBox
->paintCharacters(m_paintInfo
, m_tx
, m_ty
, *it
, stringStart
, stringLength
, activePaintServer
);
547 // Paint decorations, that have to be drawn afterwards
548 /* FIXME khtml if (textDecorations & LINE_THROUGH && textWidth != 0.0f)
549 textBox->paintDecoration(LINE_THROUGH, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);*/
551 // Skip processed characters
555 /* FIXME khtml if (!chunkCtm.isIdentity())
556 m_paintInfo.context->concatCTM(chunkCtm.inverse());*/
560 SVGRootInlineBox
* m_rootBox
;
561 bool m_chunkStarted
: 1;
563 RenderObject::PaintInfo m_paintInfo
;
564 RenderObject::PaintInfo m_savedInfo
;
566 FloatRect m_boundingBox
;
567 SVGResourceFilter
* m_filter
;
568 SVGResourceFilter
* m_rootFilter
;
570 SVGPaintServer
* m_fillPaintServer
;
571 SVGPaintServer
* m_strokePaintServer
;
573 RenderObject
* m_fillPaintServerObject
;
574 RenderObject
* m_strokePaintServerObject
;
580 void SVGRootInlineBox::paint(RenderObject::PaintInfo
& paintInfo
, int tx
, int ty
)
582 //if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground)
585 RenderObject::PaintInfo
savedInfo(paintInfo
);
586 //paintInfo.context->save();
588 SVGResourceFilter
* filter
= 0;
589 FloatRect
boundingBox(tx
+ xPos(), ty
+ yPos(), width(), height());
591 // Initialize text rendering
592 paintInfo
.p
->setWorldMatrix(object()->localTransform(), true);
593 prepareToRenderSVGContent(object(), paintInfo
, boundingBox
, filter
);
594 paintInfo
.p
->setWorldMatrix(object()->localTransform().inverse(), true);
596 //kDebug() << "paint at" << tx << ty << endl;
597 //kDebug() << "pos: (" << (tx + xPos()) << "," << (ty + yPos()) << ")" << endl;
598 //kDebug() << "size: " << width() << "x" << height() << endl;
600 // Render text, chunk-by-chunk
601 SVGRootInlineBoxPaintWalker
walkerCallback(this, filter
, paintInfo
, tx
, ty
);
602 SVGTextChunkWalker
<SVGRootInlineBoxPaintWalker
> walker(&walkerCallback
,
603 &SVGRootInlineBoxPaintWalker::chunkPortionCallback
,
604 &SVGRootInlineBoxPaintWalker::chunkStartCallback
,
605 &SVGRootInlineBoxPaintWalker::chunkEndCallback
,
606 &SVGRootInlineBoxPaintWalker::chunkSetupFillCallback
,
607 &SVGRootInlineBoxPaintWalker::chunkSetupStrokeCallback
);
609 walkTextChunks(&walker
);
611 // Finalize text rendering
612 //FIXME khtml finishRenderSVGContent(object(), paintInfo, boundingBox, filter, savedInfo.context);
613 //paintInfo.context->restore();
616 int SVGRootInlineBox::placeBoxesHorizontally(int, int& leftPosition
, int& rightPosition
, bool&)
618 // Remove any offsets caused by RTL text layout
624 void SVGRootInlineBox::verticallyAlignBoxes(int& heightOfBlock
)
626 // height is set by layoutInlineBoxes.
627 heightOfBlock
= height();
630 float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange
& range
)
632 ASSERT(!range
.isOpen());
633 ASSERT(range
.isClosed());
634 ASSERT(range
.box
->isInlineTextBox());
636 InlineTextBox
* textBox
= static_cast<InlineTextBox
*>(range
.box
);
637 RenderText
* text
= textBox
->renderText();
638 RenderStyle
* style
= text
->style();
640 /*FIXME return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox, 0));*/
644 float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange
& range
)
646 ASSERT(!range
.isOpen());
647 ASSERT(range
.isClosed());
648 ASSERT(range
.box
->isInlineTextBox());
650 InlineTextBox
* textBox
= static_cast<InlineTextBox
*>(range
.box
);
651 RenderText
* text
= textBox
->renderText();
652 /*FIXME const Font& font = text->style()->font();
654 return (range.endOffset - range.startOffset) * (font.ascent() + font.descent());*/
658 /*TextRun svgTextRunForInlineTextBox(const UChar* c, int len, RenderStyle* style, const InlineTextBox* textBox, float xPos)
663 TextRun run(c, len, false, static_cast<int>(xPos), textBox->toAdd(), textBox->m_reversed, textBox->m_dirOverride || style->visuallyOrdered());
665 #if ENABLE(SVG_FONTS)
666 run.setReferencingRenderObject(textBox->textObject()->parent());
669 // We handle letter & word spacing ourselves
670 run.disableSpacing();
674 static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk
& chunk
, bool calcWidthOnly
)
677 Vector
<SVGChar
>::iterator charIt
= chunk
.start
;
679 Vector
<SVGInlineBoxCharacterRange
>::iterator it
= chunk
.boxes
.begin();
680 Vector
<SVGInlineBoxCharacterRange
>::iterator end
= chunk
.boxes
.end();
682 for (; it
!= end
; ++it
) {
683 SVGInlineBoxCharacterRange
& range
= *it
;
685 SVGInlineTextBox
* box
= static_cast<SVGInlineTextBox
*>(range
.box
);
686 RenderStyle
* style
= box
->object()->style();
688 for (int i
= range
.startOffset
; i
< range
.endOffset
; ++i
) {
689 ASSERT(charIt
<= chunk
.end
);
691 // Determine how many characters - starting from the current - can be measured at once.
692 // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width
693 // of a string is not the sum of the boundaries of all contained glyphs.
694 Vector
<SVGChar
>::iterator itSearch
= charIt
+ 1;
695 Vector
<SVGChar
>::iterator endSearch
= charIt
+ range
.endOffset
- i
;
696 while (itSearch
!= endSearch
) {
697 // No need to check for 'isHidden()' here as this function is not called for text paths.
698 if (itSearch
->drawnSeperated
)
704 unsigned int positionOffset
= itSearch
- charIt
;
706 // Calculate width/height of subrange
707 SVGInlineBoxCharacterRange subRange
;
708 subRange
.box
= range
.box
;
709 subRange
.startOffset
= i
;
710 subRange
.endOffset
= i
+ positionOffset
;
713 length
+= cummulatedWidthOfInlineBoxCharacterRange(subRange
);
715 length
+= cummulatedHeightOfInlineBoxCharacterRange(subRange
);
717 // Calculate gap between the previous & current range
718 // <text x="10 50 70">ABCD</text> - we need to take the gaps between A & B into account
719 // so add "40" as width, and analogous for B & C, add "20" as width.
720 if (itSearch
> chunk
.start
&& itSearch
< chunk
.end
) {
721 SVGChar
& lastCharacter
= *(itSearch
- 1);
722 SVGChar
& currentCharacter
= *itSearch
;
724 int offset
= box
->m_reversed
? box
->end() - i
- positionOffset
+ 1 : box
->start() + i
+ positionOffset
- 1;
726 // FIXME: does this need to change to handle multichar glyphs?
727 int charsConsumed
= 1;
730 float lastGlyphWidth
= box
->calculateGlyphWidth(style
, offset
, 0, charsConsumed
, glyphName
);
731 length
+= currentCharacter
.x
- lastCharacter
.x
- lastGlyphWidth
;
733 float lastGlyphHeight
= box
->calculateGlyphHeight(style
, offset
, 0);
734 length
+= currentCharacter
.y
- lastCharacter
.y
- lastGlyphHeight
;
738 // Advance processed characters
739 i
+= positionOffset
- 1;
744 ASSERT(charIt
== chunk
.end
);
748 static float cummulatedWidthOfTextChunk(SVGTextChunk
& chunk
)
750 return cummulatedWidthOrHeightOfTextChunk(chunk
, true);
753 static float cummulatedHeightOfTextChunk(SVGTextChunk
& chunk
)
755 return cummulatedWidthOrHeightOfTextChunk(chunk
, false);
758 static float calculateTextAnchorShiftForTextChunk(SVGTextChunk
& chunk
, ETextAnchor anchor
)
762 if (chunk
.isVerticalText
)
763 shift
= cummulatedHeightOfTextChunk(chunk
);
765 shift
= cummulatedWidthOfTextChunk(chunk
);
767 if (anchor
== TA_MIDDLE
)
775 static void applyTextAnchorToTextChunk(SVGTextChunk
& chunk
)
777 // This method is not called for chunks containing chars aligned on a path.
778 // -> all characters are visible, no need to check for "isHidden()" anywhere.
780 if (chunk
.anchor
== TA_START
)
783 float shift
= calculateTextAnchorShiftForTextChunk(chunk
, chunk
.anchor
);
785 // Apply correction to chunk
786 Vector
<SVGChar
>::iterator chunkIt
= chunk
.start
;
787 for (; chunkIt
!= chunk
.end
; ++chunkIt
) {
788 SVGChar
& curChar
= *chunkIt
;
790 if (chunk
.isVerticalText
)
797 Vector
<SVGInlineBoxCharacterRange
>::iterator boxIt
= chunk
.boxes
.begin();
798 Vector
<SVGInlineBoxCharacterRange
>::iterator boxEnd
= chunk
.boxes
.end();
800 for (; boxIt
!= boxEnd
; ++boxIt
) {
801 SVGInlineBoxCharacterRange
& range
= *boxIt
;
803 InlineBox
* curBox
= range
.box
;
804 ASSERT(curBox
->isInlineTextBox());
805 ASSERT(curBox
->parent() && (curBox
->parent()->isRootInlineBox() || curBox
->parent()->isInlineFlowBox()));
808 if (chunk
.isVerticalText
)
809 curBox
->setYPos(curBox
->yPos() + static_cast<int>(shift
));
811 curBox
->setXPos(curBox
->xPos() + static_cast<int>(shift
));
815 static float calculateTextLengthCorrectionForTextChunk(SVGTextChunk
& chunk
, ELengthAdjust lengthAdjust
, float& computedLength
)
817 //kDebug() << "text length" << endl;
818 if (chunk
.textLength
<= 0.0f
)
821 float computedWidth
= cummulatedWidthOfTextChunk(chunk
);
822 float computedHeight
= cummulatedHeightOfTextChunk(chunk
);
824 if ((computedWidth
<= 0.0f
&& !chunk
.isVerticalText
) ||
825 (computedHeight
<= 0.0f
&& chunk
.isVerticalText
))
828 if (chunk
.isVerticalText
)
829 computedLength
= computedHeight
;
831 computedLength
= computedWidth
;
833 if (lengthAdjust
== SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS
) {
834 if (chunk
.isVerticalText
)
835 chunk
.ctm
.scale(1.0f
, chunk
.textLength
/ computedLength
);
837 chunk
.ctm
.scale(chunk
.textLength
/ computedLength
, 1.0f
);
842 return (chunk
.textLength
- computedLength
) / float(chunk
.end
- chunk
.start
);
845 static void applyTextLengthCorrectionToTextChunk(SVGTextChunk
& chunk
)
847 // This method is not called for chunks containing chars aligned on a path.
848 // -> all characters are visible, no need to check for "isHidden()" anywhere.
850 // lengthAdjust="spacingAndGlyphs" is handled by modifying chunk.ctm
851 float computedLength
= 0.0f
;
852 float spacingToApply
= calculateTextLengthCorrectionForTextChunk(chunk
, chunk
.lengthAdjust
, computedLength
);
854 if (!chunk
.ctm
.isIdentity() && chunk
.lengthAdjust
== SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS
) {
855 SVGChar
& firstChar
= *(chunk
.start
);
857 // Assure we apply the chunk scaling in the right origin
858 AffineTransform newChunkCtm
;
859 newChunkCtm
.translate(firstChar
.x
, firstChar
.y
);
860 newChunkCtm
= chunk
.ctm
* newChunkCtm
;
861 newChunkCtm
.translate(-firstChar
.x
, -firstChar
.y
);
863 chunk
.ctm
= newChunkCtm
;
866 // Apply correction to chunk
867 if (spacingToApply
!= 0.0f
) {
868 Vector
<SVGChar
>::iterator chunkIt
= chunk
.start
;
869 for (; chunkIt
!= chunk
.end
; ++chunkIt
) {
870 SVGChar
& curChar
= *chunkIt
;
871 curChar
.drawnSeperated
= true;
873 if (chunk
.isVerticalText
)
874 curChar
.y
+= (chunkIt
- chunk
.start
) * spacingToApply
;
876 curChar
.x
+= (chunkIt
- chunk
.start
) * spacingToApply
;
881 void SVGRootInlineBox::computePerCharacterLayoutInformation()
883 //kDebug() << "computePerCharacterLayoutInformation()" << endl;
884 // Clean up any previous layout information
886 m_svgTextChunks
.clear();
888 // Build layout information for all contained render objects
889 SVGCharacterLayoutInfo
info(m_svgChars
);
890 //kDebug() << "before build layout info" << endl;
891 buildLayoutInformation(this, info
);
892 //kDebug() << "after build layout info" << endl;
894 // Now all layout information are available for every character
895 // contained in any of our child inline/flow boxes. Build list
896 // of text chunks now, to be able to apply text-anchor shifts.
897 buildTextChunks(m_svgChars
, m_svgTextChunks
, this);
898 //kDebug() << "after build text chunks" << endl;
900 // Layout all text chunks
901 // text-anchor needs to be applied to individual chunks.
903 //kDebug() << "after layout text chunks" << endl;
905 // Finally the top left position of our box is known.
906 // Propagate this knownledge to our RenderSVGText parent.
907 FloatPoint topLeft
= topLeftPositionOfCharacterRange(m_svgChars
);
908 object()->setPos((int) floorf(topLeft
.x()), (int) floorf(topLeft
.y()));
910 // Layout all InlineText/Flow boxes
911 // BEWARE: This requires the root top/left position to be set correctly before!
912 //kDebug() << "before layout inline boxes" << endl;
914 //kDebug() << "at the end" << endl;
917 void SVGRootInlineBox::buildLayoutInformation(InlineFlowBox
* start
, SVGCharacterLayoutInfo
& info
)
919 if (start
->isRootInlineBox()) {
920 ASSERT(start
->object()->element()->hasTagName(SVGNames::textTag
));
922 SVGTextPositioningElement
* positioningElement
= static_cast<SVGTextPositioningElement
*>(start
->object()->element());
923 ASSERT(positioningElement
);
924 ASSERT(positioningElement
->parentNode());
926 info
.addLayoutInformation(positioningElement
);
930 LastGlyphInfo lastGlyph
;
932 for (InlineBox
* curr
= start
->firstChild(); curr
; curr
= curr
->nextOnLine()) {
933 if (curr
->object()->isText())
934 buildLayoutInformationForTextBox(info
, static_cast<InlineTextBox
*>(curr
), lastGlyph
);
936 ASSERT(curr
->isInlineFlowBox());
937 InlineFlowBox
* flowBox
= static_cast<InlineFlowBox
*>(curr
);
939 bool isAnchor
= flowBox
->object()->element()->hasTagName(SVGNames::aTag
);
940 bool isTextPath
= flowBox
->object()->element()->hasTagName(SVGNames::textPathTag
);
942 if (!isTextPath
&& !isAnchor
) {
943 SVGTextPositioningElement
* positioningElement
= static_cast<SVGTextPositioningElement
*>(flowBox
->object()->element());
944 ASSERT(positioningElement
);
945 ASSERT(positioningElement
->parentNode());
947 info
.addLayoutInformation(positioningElement
);
948 } else if (!isAnchor
) {
949 info
.setInPathLayout(true);
951 // Handle text-anchor/textLength on path, which is special.
952 SVGTextContentElement
* textContent
= 0;
953 Node
* node
= flowBox
->object()->element();
954 if (node
&& node
->isSVGElement())
955 textContent
= static_cast<SVGTextContentElement
*>(node
);
958 ELengthAdjust lengthAdjust
= (ELengthAdjust
) textContent
->lengthAdjust();
959 ETextAnchor anchor
= flowBox
->object()->style()->svgStyle()->textAnchor();
960 float textAnchorStartOffset
= 0.0f
;
962 // Initialize sub-layout. We need to create text chunks from the textPath
963 // children using our standard layout code, to be able to measure the
964 // text length using our normal methods and not textPath specific hacks.
965 Vector
<SVGChar
> tempChars
;
966 Vector
<SVGTextChunk
> tempChunks
;
968 SVGCharacterLayoutInfo
tempInfo(tempChars
);
969 buildLayoutInformation(flowBox
, tempInfo
);
971 buildTextChunks(tempChars
, tempChunks
, flowBox
);
973 Vector
<SVGTextChunk
>::iterator it
= tempChunks
.begin();
974 Vector
<SVGTextChunk
>::iterator end
= tempChunks
.end();
977 float computedLength
= 0.0f
;
979 for (; it
!= end
; ++it
) {
980 SVGTextChunk
& chunk
= *it
;
982 // Apply text-length calculation
983 info
.pathExtraAdvance
+= calculateTextLengthCorrectionForTextChunk(chunk
, lengthAdjust
, computedLength
);
985 if (lengthAdjust
== SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS
) {
986 info
.pathTextLength
+= computedLength
;
987 info
.pathChunkLength
+= chunk
.textLength
;
990 // Calculate text-anchor start offset
991 if (anchor
== TA_START
)
994 textAnchorStartOffset
+= calculateTextAnchorShiftForTextChunk(chunk
, anchor
);
997 info
.addLayoutInformation(flowBox
, textAnchorStartOffset
);
1000 float shiftxSaved
= info
.shiftx
;
1001 float shiftySaved
= info
.shifty
;
1003 buildLayoutInformation(flowBox
, info
);
1004 info
.processedChunk(shiftxSaved
, shiftySaved
);
1007 info
.setInPathLayout(false);
1012 void SVGRootInlineBox::layoutInlineBoxes()
1016 int highX
= INT_MIN
;
1017 int highY
= INT_MIN
;
1019 // Layout all child boxes
1020 Vector
<SVGChar
>::iterator it
= m_svgChars
.begin();
1021 layoutInlineBoxes(this, it
, lowX
, highX
, lowY
, highY
);
1022 ASSERT(it
== m_svgChars
.end());
1025 void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox
* start
, Vector
<SVGChar
>::iterator
& it
, int& lowX
, int& highX
, int& lowY
, int& highY
)
1027 for (InlineBox
* curr
= start
->firstChild(); curr
; curr
= curr
->nextOnLine()) {
1028 RenderStyle
* style
= curr
->object()->style();
1029 const Font
& font
= style
->htmlFont();
1031 if (curr
->object()->isText()) {
1032 SVGInlineTextBox
* textBox
= static_cast<SVGInlineTextBox
*>(curr
);
1033 unsigned length
= textBox
->len();
1035 SVGChar curChar
= *it
;
1036 ASSERT(it
!= m_svgChars
.end());
1038 FloatRect stringRect
;
1039 for (unsigned i
= 0; i
< length
; ++i
) {
1040 ASSERT(it
!= m_svgChars
.end());
1042 if (it
->isHidden()) {
1047 stringRect
.unite(textBox
->calculateGlyphBoundaries(style
, textBox
->start() + i
, *it
));
1051 IntRect enclosedStringRect
= enclosingIntRect(stringRect
);
1053 int minX
= enclosedStringRect
.x();
1054 int maxX
= minX
+ enclosedStringRect
.width();
1056 int minY
= enclosedStringRect
.y();
1057 int maxY
= minY
+ enclosedStringRect
.height();
1059 curr
->setXPos(minX
- object()->xPos());
1060 curr
->setWidth(enclosedStringRect
.width());
1062 curr
->setYPos(minY
- object()->yPos());
1063 curr
->setBaseline(font
.ascent());
1064 curr
->setHeight(enclosedStringRect
.height());
1078 ASSERT(curr
->isInlineFlowBox());
1085 InlineFlowBox
* flowBox
= static_cast<InlineFlowBox
*>(curr
);
1086 layoutInlineBoxes(flowBox
, it
, minX
, maxX
, minY
, maxY
);
1088 curr
->setXPos(minX
- object()->xPos());
1089 curr
->setWidth(maxX
- minX
);
1091 curr
->setYPos(minY
- object()->yPos());
1092 curr
->setBaseline(font
.ascent());
1093 curr
->setHeight(maxY
- minY
);
1109 if (start
->isRootInlineBox()) {
1110 int top
= lowY
- object()->yPos();
1111 int bottom
= highY
- object()->yPos();
1113 start
->setXPos(lowX
- object()->xPos());
1114 start
->setYPos(top
);
1116 start
->setWidth(highX
- lowX
);
1117 start
->setHeight(highY
- lowY
);
1119 /*FIXME start->setVerticalOverflowPositions(top, bottom);
1120 start->setVerticalSelectionPositions(top, bottom);*/
1124 void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo
& info
, InlineTextBox
* textBox
, LastGlyphInfo
& lastGlyph
)
1126 RenderText
* text
= textBox
->renderText();
1129 RenderStyle
* style
= text
->style(/*textBox->isFirstLineStyle()*/);
1132 const Font
& font
= style
->htmlFont();
1133 SVGInlineTextBox
* svgTextBox
= static_cast<SVGInlineTextBox
*>(textBox
);
1135 unsigned length
= textBox
->len();
1137 const SVGRenderStyle
* svgStyle
= style
->svgStyle();
1138 bool isVerticalText
= isVerticalWritingMode(svgStyle
);
1140 int charsConsumed
= 0;
1141 for (unsigned i
= 0; i
< length
; i
+= charsConsumed
) {
1144 if (info
.inPathLayout())
1145 svgChar
.pathData
= SVGCharOnPath::create();
1147 float glyphWidth
= 0.0f
;
1148 float glyphHeight
= 0.0f
;
1150 int extraCharsAvailable
= length
- i
- 1;
1154 if (textBox
->m_reversed
) {
1155 glyphWidth
= svgTextBox
->calculateGlyphWidth(style
, textBox
->end() - i
, extraCharsAvailable
, charsConsumed
, glyphName
);
1156 glyphHeight
= svgTextBox
->calculateGlyphHeight(style
, textBox
->end() - i
, extraCharsAvailable
);
1157 unicodeStr
= String(textBox
->renderText()->text()/*->characters()*/ + textBox
->end() - i
, charsConsumed
);
1159 glyphWidth
= svgTextBox
->calculateGlyphWidth(style
, textBox
->start() + i
, extraCharsAvailable
, charsConsumed
, glyphName
);
1160 glyphHeight
= svgTextBox
->calculateGlyphHeight(style
, textBox
->start() + i
, extraCharsAvailable
);
1161 unicodeStr
= String(textBox
->renderText()->text()/*->characters()*/ + textBox
->start() + i
, charsConsumed
);
1164 bool assignedX
= false;
1165 bool assignedY
= false;
1167 if (info
.xValueAvailable() && (!info
.inPathLayout() || (info
.inPathLayout() && !isVerticalText
))) {
1168 if (!isVerticalText
)
1169 svgChar
.newTextChunk
= true;
1172 svgChar
.drawnSeperated
= true;
1173 info
.curx
= info
.xValueNext();
1176 if (info
.yValueAvailable() && (!info
.inPathLayout() || (info
.inPathLayout() && isVerticalText
))) {
1178 svgChar
.newTextChunk
= true;
1181 svgChar
.drawnSeperated
= true;
1182 info
.cury
= info
.yValueNext();
1188 // Apply x-axis shift
1189 if (info
.dxValueAvailable()) {
1190 svgChar
.drawnSeperated
= true;
1192 dx
= info
.dxValueNext();
1195 if (!info
.inPathLayout())
1199 // Apply y-axis shift
1200 if (info
.dyValueAvailable()) {
1201 svgChar
.drawnSeperated
= true;
1203 dy
= info
.dyValueNext();
1206 if (!info
.inPathLayout())
1210 // Take letter & word spacing and kerning into account
1211 float spacing
= 0;//FIXME font.letterSpacing() + calculateKerning(textBox->object()->element()->renderer());
1213 const UChar
* currentCharacter
= text
->text()/*->characters()*/ + (textBox
->m_reversed
? textBox
->end() - i
: textBox
->start() + i
);
1214 const UChar
* lastCharacter
= 0;
1216 if (textBox
->m_reversed
) {
1217 if (i
< textBox
->end())
1218 lastCharacter
= text
->text()/*characters()*/ + textBox
->end() - i
+ 1;
1221 lastCharacter
= text
->text()/*characters()*/ + textBox
->start() + i
- 1;
1224 if (info
.nextDrawnSeperated
|| spacing
!= 0.0f
) {
1225 info
.nextDrawnSeperated
= false;
1226 svgChar
.drawnSeperated
= true;
1229 /*FIXME if (currentCharacter && Font::treatAsSpace(*currentCharacter) && lastCharacter && !Font::treatAsSpace(*lastCharacter)) {
1230 spacing += font.wordSpacing();
1232 if (spacing != 0.0f && !info.inPathLayout())
1233 info.nextDrawnSeperated = true;
1236 float orientationAngle
= glyphOrientationToAngle(svgStyle
, isVerticalText
, *currentCharacter
);
1238 float xOrientationShift
= 0.0f
;
1239 float yOrientationShift
= 0.0f
;
1240 float glyphAdvance
= calculateGlyphAdvanceAndShiftRespectingOrientation(isVerticalText
, orientationAngle
, glyphWidth
, glyphHeight
,
1241 font
, svgChar
, xOrientationShift
, yOrientationShift
);
1243 // Handle textPath layout mode
1244 if (info
.inPathLayout()) {
1245 float extraAdvance
= isVerticalText
? dy
: dx
;
1246 float newOffset
= FLT_MIN
;
1248 if (assignedX
&& !isVerticalText
)
1249 newOffset
= info
.curx
;
1250 else if (assignedY
&& isVerticalText
)
1251 newOffset
= info
.cury
;
1253 float correctedGlyphAdvance
= glyphAdvance
;
1255 // Handle lengthAdjust="spacingAndGlyphs" by specifying per-character scale operations
1256 if (info
.pathTextLength
> 0.0f
&& info
.pathChunkLength
> 0.0f
) {
1257 if (isVerticalText
) {
1258 svgChar
.pathData
->yScale
= info
.pathChunkLength
/ info
.pathTextLength
;
1259 spacing
*= svgChar
.pathData
->yScale
;
1260 correctedGlyphAdvance
*= svgChar
.pathData
->yScale
;
1262 svgChar
.pathData
->xScale
= info
.pathChunkLength
/ info
.pathTextLength
;
1263 spacing
*= svgChar
.pathData
->xScale
;
1264 correctedGlyphAdvance
*= svgChar
.pathData
->xScale
;
1268 // Handle letter & word spacing on text path
1269 float pathExtraAdvance
= info
.pathExtraAdvance
;
1270 info
.pathExtraAdvance
+= spacing
;
1272 svgChar
.pathData
->hidden
= !info
.nextPathLayoutPointAndAngle(correctedGlyphAdvance
, extraAdvance
, newOffset
);
1273 svgChar
.drawnSeperated
= true;
1275 info
.pathExtraAdvance
= pathExtraAdvance
;
1279 if (info
.angleValueAvailable())
1280 info
.angle
= info
.angleValueNext();
1282 // Apply baseline-shift
1283 if (info
.baselineShiftValueAvailable()) {
1284 svgChar
.drawnSeperated
= true;
1285 float shift
= info
.baselineShiftValueNext();
1288 info
.shiftx
+= shift
;
1290 info
.shifty
-= shift
;
1293 // Take dominant-baseline / alignment-baseline into account
1294 yOrientationShift
+= alignmentBaselineToShift(isVerticalText
, text
, font
);
1296 svgChar
.x
= info
.curx
;
1297 svgChar
.y
= info
.cury
;
1298 svgChar
.angle
= info
.angle
;
1300 // For text paths any shift (dx/dy/baseline-shift) has to be applied after the rotation
1301 if (!info
.inPathLayout()) {
1302 svgChar
.x
+= info
.shiftx
+ xOrientationShift
;
1303 svgChar
.y
+= info
.shifty
+ yOrientationShift
;
1305 if (orientationAngle
!= 0.0f
)
1306 svgChar
.angle
+= orientationAngle
;
1308 if (svgChar
.angle
!= 0.0f
)
1309 svgChar
.drawnSeperated
= true;
1311 svgChar
.pathData
->orientationAngle
= orientationAngle
;
1314 svgChar
.angle
-= 90.0f
;
1316 svgChar
.pathData
->xShift
= info
.shiftx
+ xOrientationShift
;
1317 svgChar
.pathData
->yShift
= info
.shifty
+ yOrientationShift
;
1319 // Translate to glyph midpoint
1320 if (isVerticalText
) {
1321 svgChar
.pathData
->xShift
+= info
.dx
;
1322 svgChar
.pathData
->yShift
-= glyphAdvance
/ 2.0f
;
1324 svgChar
.pathData
->xShift
-= glyphAdvance
/ 2.0f
;
1325 svgChar
.pathData
->yShift
+= info
.dy
;
1329 double kerning
= 0.0;
1330 #if ENABLE(SVG_FONTS)
1331 /*FIXME khtml SVGFontElement* svgFont = 0;
1332 if (style->font().isSVGFont())
1333 svgFont = style->font().svgFont();
1335 if (lastGlyph.isValid && style->font().isSVGFont()) {
1336 SVGHorizontalKerningPair kerningPair;
1337 if (svgFont->getHorizontalKerningPairForStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeStr, glyphName, kerningPair))
1338 kerning = kerningPair.kerning;
1341 if (style->font().isSVGFont()) {
1342 lastGlyph.unicode = unicodeStr;
1343 lastGlyph.glyphName = glyphName;
1344 lastGlyph.isValid = true;
1346 lastGlyph.isValid = false;*/
1349 svgChar
.x
-= (float)kerning
;
1351 // Advance to new position
1352 if (isVerticalText
) {
1353 svgChar
.drawnSeperated
= true;
1354 info
.cury
+= glyphAdvance
+ spacing
;
1356 info
.curx
+= glyphAdvance
+ spacing
- (float)kerning
;
1358 // Advance to next character group
1359 for (int k
= 0; k
< charsConsumed
; ++k
) {
1360 info
.svgChars
.append(svgChar
);
1361 info
.processedSingleCharacter();
1362 svgChar
.drawnSeperated
= false;
1363 svgChar
.newTextChunk
= false;
1368 void SVGRootInlineBox::buildTextChunks(Vector
<SVGChar
>& svgChars
, Vector
<SVGTextChunk
>& svgTextChunks
, InlineFlowBox
* start
)
1370 SVGTextChunkLayoutInfo
info(svgTextChunks
);
1371 info
.it
= svgChars
.begin();
1372 info
.chunk
.start
= svgChars
.begin();
1373 info
.chunk
.end
= svgChars
.begin();
1375 buildTextChunks(svgChars
, start
, info
);
1376 ASSERT(info
.it
== svgChars
.end());
1379 void SVGRootInlineBox::buildTextChunks(Vector
<SVGChar
>& svgChars
, InlineFlowBox
* start
, SVGTextChunkLayoutInfo
& info
)
1381 #if DEBUG_CHUNK_BUILDING > 1
1382 fprintf(stderr
, " -> buildTextChunks(start=%p)\n", start
);
1385 for (InlineBox
* curr
= start
->firstChild(); curr
; curr
= curr
->nextOnLine()) {
1386 if (curr
->object()->isText()) {
1387 InlineTextBox
* textBox
= static_cast<InlineTextBox
*>(curr
);
1389 unsigned length
= textBox
->len();
1393 #if DEBUG_CHUNK_BUILDING > 1
1394 fprintf(stderr
, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n",
1395 textBox
, length
, textBox
->start(), textBox
->end(), (int) info
.handlingTextPath
);
1398 RenderText
* text
= textBox
->renderText();
1400 ASSERT(text
->element());
1402 SVGTextContentElement
* textContent
= 0;
1403 Node
* node
= text
->element()->parent();
1404 if (node
&& node
->isSVGElement())
1405 textContent
= static_cast<SVGTextContentElement
*>(node
);
1406 ASSERT(textContent
);
1408 // Start new character range for the first chunk
1409 bool isFirstCharacter
= info
.svgTextChunks
.isEmpty() && info
.chunk
.start
== info
.it
&& info
.chunk
.start
== info
.chunk
.end
;
1410 if (isFirstCharacter
) {
1411 ASSERT(info
.chunk
.boxes
.isEmpty());
1412 info
.chunk
.boxes
.append(SVGInlineBoxCharacterRange());
1414 ASSERT(!info
.chunk
.boxes
.isEmpty());
1416 // Walk string to find out new chunk positions, if existent
1417 for (unsigned i
= 0; i
< length
; ++i
) {
1418 ASSERT(info
.it
!= svgChars
.end());
1420 SVGInlineBoxCharacterRange
& range
= info
.chunk
.boxes
.last();
1421 if (range
.isOpen()) {
1423 range
.startOffset
= (i
== 0 ? 0 : i
- 1);
1425 #if DEBUG_CHUNK_BUILDING > 1
1426 fprintf(stderr
, " | -> Range is open! box=%p, startOffset=%i\n", range
.box
, range
.startOffset
);
1430 // If a new (or the first) chunk has been started, record it's text-anchor and writing mode.
1431 if (info
.assignChunkProperties
) {
1432 info
.assignChunkProperties
= false;
1434 info
.chunk
.isVerticalText
= isVerticalWritingMode(text
->style()->svgStyle());
1435 info
.chunk
.isTextPath
= info
.handlingTextPath
;
1436 info
.chunk
.anchor
= text
->style()->svgStyle()->textAnchor();
1437 info
.chunk
.textLength
= textContent
->textLength().value();
1438 info
.chunk
.lengthAdjust
= (ELengthAdjust
) textContent
->lengthAdjust();
1440 #if DEBUG_CHUNK_BUILDING > 1
1441 fprintf(stderr
, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", info
.chunk
.isVerticalText
, info
.chunk
.anchor
);
1445 if (i
> 0 && !isFirstCharacter
&& (*info
.it
).newTextChunk
) {
1446 // Close mid chunk & character range
1447 ASSERT(!range
.isOpen());
1448 ASSERT(!range
.isClosed());
1450 range
.endOffset
= i
;
1451 closeTextChunk(info
);
1453 #if DEBUG_CHUNK_BUILDING > 1
1454 fprintf(stderr
, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range
.endOffset
);
1457 // Prepare for next chunk, if we're not at the end
1458 startTextChunk(info
);
1459 if (i
+ 1 == length
) {
1460 #if DEBUG_CHUNK_BUILDING > 1
1461 fprintf(stderr
, " | -> Record last chunk of inline text box!\n");
1464 startTextChunk(info
);
1465 SVGInlineBoxCharacterRange
& range
= info
.chunk
.boxes
.last();
1467 info
.assignChunkProperties
= false;
1468 info
.chunk
.isVerticalText
= isVerticalWritingMode(text
->style()->svgStyle());
1469 info
.chunk
.isTextPath
= info
.handlingTextPath
;
1470 info
.chunk
.anchor
= text
->style()->svgStyle()->textAnchor();
1471 info
.chunk
.textLength
= textContent
->textLength().value();
1472 info
.chunk
.lengthAdjust
= (ELengthAdjust
) textContent
->lengthAdjust();
1475 range
.startOffset
= i
;
1477 ASSERT(!range
.isOpen());
1478 ASSERT(!range
.isClosed());
1482 // This should only hold true for the first character of the first chunk
1483 if (isFirstCharacter
)
1484 isFirstCharacter
= false;
1489 #if DEBUG_CHUNK_BUILDING > 1
1490 fprintf(stderr
, " -> Finished inline text box!\n");
1493 SVGInlineBoxCharacterRange
& range
= info
.chunk
.boxes
.last();
1494 if (!range
.isOpen() && !range
.isClosed()) {
1495 #if DEBUG_CHUNK_BUILDING > 1
1496 fprintf(stderr
, " -> Last range not closed - closing with endOffset: %i\n", length
);
1499 // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk.
1500 range
.endOffset
= length
;
1502 if (info
.it
!= svgChars
.end()) {
1503 #if DEBUG_CHUNK_BUILDING > 1
1504 fprintf(stderr
, " -> Not at last character yet!\n");
1507 // If we're not at the end of the last box to be processed, and if the next
1508 // character starts a new chunk, then close the current chunk and start a new one.
1509 if ((*info
.it
).newTextChunk
) {
1510 #if DEBUG_CHUNK_BUILDING > 1
1511 fprintf(stderr
, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n");
1514 closeTextChunk(info
);
1515 startTextChunk(info
);
1517 // Just start a new character range
1518 info
.chunk
.boxes
.append(SVGInlineBoxCharacterRange());
1520 #if DEBUG_CHUNK_BUILDING > 1
1521 fprintf(stderr
, " -> Next character does NOT start a new chunk! Starting new character range...\n");
1525 #if DEBUG_CHUNK_BUILDING > 1
1526 fprintf(stderr
, " -> Closing final chunk! Finished processing!\n");
1529 // Close final chunk, once we're at the end of the last box
1530 closeTextChunk(info
);
1534 ASSERT(curr
->isInlineFlowBox());
1535 InlineFlowBox
* flowBox
= static_cast<InlineFlowBox
*>(curr
);
1537 bool isTextPath
= flowBox
->object()->element()->hasTagName(SVGNames::textPathTag
);
1539 #if DEBUG_CHUNK_BUILDING > 1
1540 fprintf(stderr
, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox
, (int) isTextPath
);
1544 info
.handlingTextPath
= true;
1546 buildTextChunks(svgChars
, flowBox
, info
);
1549 info
.handlingTextPath
= false;
1553 #if DEBUG_CHUNK_BUILDING > 1
1554 fprintf(stderr
, " <- buildTextChunks(start=%p)\n", start
);
1558 const Vector
<SVGTextChunk
>& SVGRootInlineBox::svgTextChunks() const
1560 return m_svgTextChunks
;
1563 void SVGRootInlineBox::layoutTextChunks()
1565 Vector
<SVGTextChunk
>::iterator it
= m_svgTextChunks
.begin();
1566 Vector
<SVGTextChunk
>::iterator end
= m_svgTextChunks
.end();
1568 for (; it
!= end
; ++it
) {
1569 SVGTextChunk
& chunk
= *it
;
1571 #if DEBUG_CHUNK_BUILDING > 0
1573 fprintf(stderr
, "Handle TEXT CHUNK! anchor=%i, textLength=%f, lengthAdjust=%i, isVerticalText=%i, isTextPath=%i start=%p, end=%p -> dist: %i\n",
1574 (int) chunk
.anchor
, chunk
.textLength
, (int) chunk
.lengthAdjust
, (int) chunk
.isVerticalText
,
1575 (int) chunk
.isTextPath
, chunk
.start
, chunk
.end
, (unsigned int) (chunk
.end
- chunk
.start
));
1577 Vector
<SVGInlineBoxCharacterRange
>::iterator boxIt
= chunk
.boxes
.begin();
1578 Vector
<SVGInlineBoxCharacterRange
>::iterator boxEnd
= chunk
.boxes
.end();
1581 for (; boxIt
!= boxEnd
; ++boxIt
) {
1582 SVGInlineBoxCharacterRange
& range
= *boxIt
; i
++;
1583 fprintf(stderr
, " -> RANGE %i STARTOFFSET: %i, ENDOFFSET: %i, BOX: %p\n", i
, range
.startOffset
, range
.endOffset
, range
.box
);
1588 if (chunk
.isTextPath
)
1591 // text-path & textLength, with lengthAdjust="spacing" is already handled for textPath layouts.
1592 applyTextLengthCorrectionToTextChunk(chunk
);
1594 // text-anchor is already handled for textPath layouts.
1595 applyTextAnchorToTextChunk(chunk
);
1599 static inline void addPaintServerToTextDecorationInfo(ETextDecoration decoration
, SVGTextDecorationInfo
& info
, RenderObject
* object
)
1601 if (object
->style()->svgStyle()->hasFill())
1602 info
.fillServerMap
.set(decoration
, object
);
1604 if (object
->style()->svgStyle()->hasStroke())
1605 info
.strokeServerMap
.set(decoration
, object
);
1608 SVGTextDecorationInfo
SVGRootInlineBox::retrievePaintServersForTextDecoration(RenderObject
* start
)
1612 Vector
<RenderObject
*> parentChain
;
1613 while ((start
= start
->parent())) {
1614 parentChain
.prepend(start
);
1616 // Stop at our direct <text> parent.
1617 if (start
->isSVGText())
1621 Vector
<RenderObject
*>::iterator it
= parentChain
.begin();
1622 Vector
<RenderObject
*>::iterator end
= parentChain
.end();
1624 SVGTextDecorationInfo info
;
1626 for (; it
!= end
; ++it
) {
1627 RenderObject
* object
= *it
;
1630 RenderStyle
* style
= object
->style();
1633 int decorations
= style
->textDecoration();
1634 if (decorations
!= NONE
) {
1635 if (decorations
& OVERLINE
)
1636 addPaintServerToTextDecorationInfo(OVERLINE
, info
, object
);
1638 if (decorations
& UNDERLINE
)
1639 addPaintServerToTextDecorationInfo(UNDERLINE
, info
, object
);
1641 if (decorations
& LINE_THROUGH
)
1642 addPaintServerToTextDecorationInfo(LINE_THROUGH
, info
, object
);
1649 void SVGRootInlineBox::walkTextChunks(SVGTextChunkWalkerBase
* walker
, const SVGInlineTextBox
* textBox
)
1653 Vector
<SVGTextChunk
>::iterator it
= m_svgTextChunks
.begin();
1654 Vector
<SVGTextChunk
>::iterator itEnd
= m_svgTextChunks
.end();
1656 for (; it
!= itEnd
; ++it
) {
1657 SVGTextChunk
& curChunk
= *it
;
1659 Vector
<SVGInlineBoxCharacterRange
>::iterator boxIt
= curChunk
.boxes
.begin();
1660 Vector
<SVGInlineBoxCharacterRange
>::iterator boxEnd
= curChunk
.boxes
.end();
1662 InlineBox
* lastNotifiedBox
= 0;
1663 InlineBox
* prevBox
= 0;
1665 unsigned int chunkOffset
= 0;
1666 bool startedFirstChunk
= false;
1668 for (; boxIt
!= boxEnd
; ++boxIt
) {
1669 SVGInlineBoxCharacterRange
& range
= *boxIt
;
1671 ASSERT(range
.box
->isInlineTextBox());
1672 SVGInlineTextBox
* rangeTextBox
= static_cast<SVGInlineTextBox
*>(range
.box
);
1674 if (textBox
&& rangeTextBox
!= textBox
) {
1675 chunkOffset
+= range
.endOffset
- range
.startOffset
;
1679 // Eventually notify that we started a new chunk
1680 if (!textBox
&& !startedFirstChunk
) {
1681 startedFirstChunk
= true;
1683 lastNotifiedBox
= range
.box
;
1684 walker
->start(range
.box
);
1686 // Eventually apply new style, as this chunk spans multiple boxes (with possible different styling)
1687 if (prevBox
&& prevBox
!= range
.box
) {
1688 lastNotifiedBox
= range
.box
;
1690 walker
->end(prevBox
);
1691 walker
->start(lastNotifiedBox
);
1695 unsigned int length
= range
.endOffset
- range
.startOffset
;
1697 Vector
<SVGChar
>::iterator itCharBegin
= curChunk
.start
+ chunkOffset
;
1698 Vector
<SVGChar
>::iterator itCharEnd
= curChunk
.start
+ chunkOffset
+ length
;
1699 ASSERT(itCharEnd
<= curChunk
.end
);
1701 // Process this chunk portion
1703 (*walker
)(rangeTextBox
, range
.startOffset
, curChunk
.ctm
, itCharBegin
, itCharEnd
);
1705 if (walker
->setupFill(range
.box
))
1706 (*walker
)(rangeTextBox
, range
.startOffset
, curChunk
.ctm
, itCharBegin
, itCharEnd
);
1708 if (walker
->setupStroke(range
.box
))
1709 (*walker
)(rangeTextBox
, range
.startOffset
, curChunk
.ctm
, itCharBegin
, itCharEnd
);
1712 chunkOffset
+= length
;
1715 prevBox
= range
.box
;
1718 if (!textBox
&& startedFirstChunk
)
1719 walker
->end(lastNotifiedBox
);
1723 } // namespace WebCore
1725 #endif // ENABLE(SVG)