1 /*
2 * This file is part of the WebKit project.
3 *
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>
7 *
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.
12 *
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.
17 *
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.
22 *
23 */
24
25 #include "config.h"
26
27 #if ENABLE(SVG)
28 #include "SVGRootInlineBox.h"
29
30 #include "Editor.h"
31 #include "Frame.h"
32 #include "GraphicsContext.h"
33 #include "RenderBlock.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"
44 #include "Text.h"
45 #include "UnicodeRange.h"
46
47 #include <float.h>
48
49 // Text chunk creation is complex and the whole process
50 // can easily be traced by setting this variable > 0.
51 #define DEBUG_CHUNK_BUILDING 0
52
53 namespace WebCore {
54
isVerticalWritingMode(const SVGRenderStyle * style)55 static inline bool isVerticalWritingMode(const SVGRenderStyle* style)
56 {
57 return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB;
58 }
59
dominantBaselineToShift(bool isVerticalText,const RenderObject * text,const Font & font)60 static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font)
61 {
62 ASSERT(text);
63
64 const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0;
65 ASSERT(style);
66
67 const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0;
68
69 EDominantBaseline baseline = style->dominantBaseline();
70 if (baseline == DB_AUTO) {
71 if (isVerticalText)
72 baseline = DB_CENTRAL;
73 else
74 baseline = DB_ALPHABETIC;
75 }
76
77 switch (baseline) {
78 case DB_USE_SCRIPT:
79 // TODO: The dominant-baseline and the baseline-table components are set by
80 // determining the predominant script of the character data content.
81 return AB_ALPHABETIC;
82 case DB_NO_CHANGE:
83 {
84 if (parentStyle)
85 return dominantBaselineToShift(isVerticalText, text->parent(), font);
86
87 ASSERT_NOT_REACHED();
88 return AB_AUTO;
89 }
90 case DB_RESET_SIZE:
91 {
92 if (parentStyle)
93 return dominantBaselineToShift(isVerticalText, text->parent(), font);
94
95 ASSERT_NOT_REACHED();
96 return AB_AUTO;
97 }
98 case DB_IDEOGRAPHIC:
99 return AB_IDEOGRAPHIC;
100 case DB_ALPHABETIC:
101 return AB_ALPHABETIC;
102 case DB_HANGING:
103 return AB_HANGING;
104 case DB_MATHEMATICAL:
105 return AB_MATHEMATICAL;
106 case DB_CENTRAL:
107 return AB_CENTRAL;
108 case DB_MIDDLE:
109 return AB_MIDDLE;
110 case DB_TEXT_AFTER_EDGE:
111 return AB_TEXT_AFTER_EDGE;
112 case DB_TEXT_BEFORE_EDGE:
113 return AB_TEXT_BEFORE_EDGE;
114 default:
115 ASSERT_NOT_REACHED();
116 return AB_AUTO;
117 }
118 }
119
alignmentBaselineToShift(bool isVerticalText,const RenderObject * text,const Font & font)120 static inline float alignmentBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font)
121 {
122 ASSERT(text);
123
124 const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0;
125 ASSERT(style);
126
127 const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0;
128
129 EAlignmentBaseline baseline = style->alignmentBaseline();
130 if (baseline == AB_AUTO) {
131 if (parentStyle && style->dominantBaseline() == DB_AUTO)
132 baseline = dominantBaselineToShift(isVerticalText, text->parent(), font);
133 else
134 baseline = dominantBaselineToShift(isVerticalText, text, font);
135
136 ASSERT(baseline != AB_AUTO);
137 }
138
139 // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
140 switch (baseline) {
141 case AB_BASELINE:
142 {
143 if (parentStyle)
144 return dominantBaselineToShift(isVerticalText, text->parent(), font);
145
146 return 0.0f;
147 }
148 case AB_BEFORE_EDGE:
149 case AB_TEXT_BEFORE_EDGE:
150 return font.ascent();
151 case AB_MIDDLE:
152 return font.xHeight() / 2.0f;
153 case AB_CENTRAL:
154 // Not needed, we're taking this into account already for vertical text!
155 // return (font.ascent() - font.descent()) / 2.0f;
156 return 0.0f;
157 case AB_AFTER_EDGE:
158 case AB_TEXT_AFTER_EDGE:
159 case AB_IDEOGRAPHIC:
160 return font.descent();
161 case AB_ALPHABETIC:
162 return 0.0f;
163 case AB_HANGING:
164 return font.ascent() * 8.0f / 10.0f;
165 case AB_MATHEMATICAL:
166 return font.ascent() / 2.0f;
167 default:
168 ASSERT_NOT_REACHED();
169 return 0.0f;
170 }
171 }
172
glyphOrientationToAngle(const SVGRenderStyle * svgStyle,bool isVerticalText,const UChar & character)173 static inline float glyphOrientationToAngle(const SVGRenderStyle* svgStyle, bool isVerticalText, const UChar& character)
174 {
175 switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) {
176 case GO_AUTO:
177 {
178 // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
179 // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
180 unsigned int unicodeRange = findCharUnicodeRange(character);
181 if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
182 return 90.0f;
183
184 return 0.0f;
185 }
186 case GO_90DEG:
187 return 90.0f;
188 case GO_180DEG:
189 return 180.0f;
190 case GO_270DEG:
191 return 270.0f;
192 case GO_0DEG:
193 default:
194 return 0.0f;
195 }
196 }
197
glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)198 static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
199 {
200 return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f;
201 }
202
calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText,float orientationAngle,float glyphWidth,float glyphHeight,const Font & font,SVGChar & svgChar,float & xOrientationShift,float & yOrientationShift)203 static inline float calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font& font, SVGChar& svgChar, float& xOrientationShift, float& yOrientationShift)
204 {
205 bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle);
206
207 // The function is based on spec requirements:
208 //
209 // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
210 // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
211 //
212 // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
213 // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph.
214
215 // vertical orientation handling
216 if (isVerticalText) {
217 if (orientationAngle == 0.0f) {
218 xOrientationShift = -glyphWidth / 2.0f;
219 yOrientationShift = font.ascent();
220 } else if (orientationAngle == 90.0f) {
221 xOrientationShift = -glyphHeight;
222 yOrientationShift = font.descent();
223 svgChar.orientationShiftY = -font.ascent();
224 } else if (orientationAngle == 270.0f) {
225 xOrientationShift = glyphHeight;
226 yOrientationShift = font.descent();
227 svgChar.orientationShiftX = -glyphWidth;
228 svgChar.orientationShiftY = -font.ascent();
229 } else if (orientationAngle == 180.0f) {
230 yOrientationShift = font.ascent();
231 svgChar.orientationShiftX = -glyphWidth / 2.0f;
232 svgChar.orientationShiftY = font.ascent() - font.descent();
233 }
234
235 // vertical advance calculation
236 if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees)
237 return glyphWidth;
238
239 return glyphHeight;
240 }
241
242 // horizontal orientation handling
243 if (orientationAngle == 90.0f) {
244 xOrientationShift = glyphWidth / 2.0f;
245 yOrientationShift = -font.descent();
246 svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent();
247 svgChar.orientationShiftY = font.descent();
248 } else if (orientationAngle == 270.0f) {
249 xOrientationShift = -glyphWidth / 2.0f;
250 yOrientationShift = -font.descent();
251 svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent();
252 svgChar.orientationShiftY = glyphHeight;
253 } else if (orientationAngle == 180.0f) {
254 xOrientationShift = glyphWidth / 2.0f;
255 svgChar.orientationShiftX = -glyphWidth / 2.0f;
256 svgChar.orientationShiftY = font.ascent() - font.descent();
257 }
258
259 // horizontal advance calculation
260 if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees)
261 return glyphHeight;
262
263 return glyphWidth;
264 }
265
startTextChunk(SVGTextChunkLayoutInfo & info)266 static inline void startTextChunk(SVGTextChunkLayoutInfo& info)
267 {
268 info.chunk.boxes.clear();
269 info.chunk.boxes.append(SVGInlineBoxCharacterRange());
270
271 info.chunk.start = info.it;
272 info.assignChunkProperties = true;
273 }
274
closeTextChunk(SVGTextChunkLayoutInfo & info)275 static inline void closeTextChunk(SVGTextChunkLayoutInfo& info)
276 {
277 ASSERT(!info.chunk.boxes.last().isOpen());
278 ASSERT(info.chunk.boxes.last().isClosed());
279
280 info.chunk.end = info.it;
281 ASSERT(info.chunk.end >= info.chunk.start);
282
283 info.svgTextChunks.append(info.chunk);
284 }
285
findSVGRootObject(RenderObject * start)286 RenderSVGRoot* findSVGRootObject(RenderObject* start)
287 {
288 // Find associated root inline box.
289 while (start && !start->isSVGRoot())
290 start = start->parent();
291 ASSERT(start);
292 return toRenderSVGRoot(start);
293 }
294
topLeftPositionOfCharacterRange(Vector<SVGChar> & chars)295 static inline FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>& chars)
296 {
297 return topLeftPositionOfCharacterRange(chars.begin(), chars.end());
298 }
299
topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it,Vector<SVGChar>::iterator end)300 FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::iterator end)
301 {
302 float lowX = FLT_MAX, lowY = FLT_MAX;
303 for (; it != end; ++it) {
304 if (it->isHidden())
305 continue;
306
307 float x = (*it).x;
308 float y = (*it).y;
309
310 if (x < lowX)
311 lowX = x;
312
313 if (y < lowY)
314 lowY = y;
315 }
316
317 return FloatPoint(lowX, lowY);
318 }
319
320 // Helper function
calculateKerning(RenderObject * item)321 static float calculateKerning(RenderObject* item)
322 {
323 const Font& font = item->style()->font();
324 const SVGRenderStyle* svgStyle = item->style()->svgStyle();
325
326 float kerning = 0.0f;
327 if (CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->kerning())) {
328 kerning = primitive->getFloatValue();
329
330 if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize() > 0)
331 kerning = kerning / 100.0f * font.pixelSize();
332 }
333
334 return kerning;
335 }
336
337 // Helper class for paint()
338 struct SVGRootInlineBoxPaintWalker {
SVGRootInlineBoxPaintWalkerWebCore::SVGRootInlineBoxPaintWalker339 SVGRootInlineBoxPaintWalker(SVGRootInlineBox* rootBox, SVGResourceFilter* rootFilter, RenderObject::PaintInfo paintInfo, int tx, int ty)
340 : m_rootBox(rootBox)
341 , m_chunkStarted(false)
342 , m_paintInfo(paintInfo)
343 , m_savedInfo(paintInfo)
344 , m_boundingBox(tx + rootBox->x(), ty + rootBox->y(), rootBox->width(), rootBox->height())
345 , m_filter(0)
346 , m_rootFilter(rootFilter)
347 , m_fillPaintServer(0)
348 , m_strokePaintServer(0)
349 , m_fillPaintServerObject(0)
350 , m_strokePaintServerObject(0)
351 , m_tx(tx)
352 , m_ty(ty)
353 {
354 }
355
~SVGRootInlineBoxPaintWalkerWebCore::SVGRootInlineBoxPaintWalker356 ~SVGRootInlineBoxPaintWalker()
357 {
358 ASSERT(!m_filter);
359 ASSERT(!m_fillPaintServer);
360 ASSERT(!m_fillPaintServerObject);
361 ASSERT(!m_strokePaintServer);
362 ASSERT(!m_strokePaintServerObject);
363 ASSERT(!m_chunkStarted);
364 }
365
teardownFillPaintServerWebCore::SVGRootInlineBoxPaintWalker366 void teardownFillPaintServer()
367 {
368 if (!m_fillPaintServer)
369 return;
370
371 m_fillPaintServer->teardown(m_paintInfo.context, m_fillPaintServerObject, ApplyToFillTargetType, true);
372
373 m_fillPaintServer = 0;
374 m_fillPaintServerObject = 0;
375 }
376
teardownStrokePaintServerWebCore::SVGRootInlineBoxPaintWalker377 void teardownStrokePaintServer()
378 {
379 if (!m_strokePaintServer)
380 return;
381
382 m_strokePaintServer->teardown(m_paintInfo.context, m_strokePaintServerObject, ApplyToStrokeTargetType, true);
383
384 m_strokePaintServer = 0;
385 m_strokePaintServerObject = 0;
386 }
387
chunkStartCallbackWebCore::SVGRootInlineBoxPaintWalker388 void chunkStartCallback(InlineBox* box)
389 {
390 ASSERT(!m_chunkStarted);
391 m_chunkStarted = true;
392
393 InlineFlowBox* flowBox = box->parent();
394
395 // Initialize text rendering
396 RenderObject* object = flowBox->renderer();
397 ASSERT(object);
398
399 m_savedInfo = m_paintInfo;
400 m_paintInfo.context->save();
401
402 // FIXME: Why is this done here instead of in RenderSVGText?
403 if (!flowBox->isRootInlineBox())
404 SVGRenderBase::prepareToRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_rootFilter);
405 }
406
chunkEndCallbackWebCore::SVGRootInlineBoxPaintWalker407 void chunkEndCallback(InlineBox* box)
408 {
409 ASSERT(m_chunkStarted);
410 m_chunkStarted = false;
411
412 InlineFlowBox* flowBox = box->parent();
413
414 RenderObject* object = flowBox->renderer();
415 ASSERT(object);
416
417 // Clean up last used paint server
418 teardownFillPaintServer();
419 teardownStrokePaintServer();
420
421 // Finalize text rendering
422 if (!flowBox->isRootInlineBox()) {
423 SVGRenderBase::finishRenderSVGContent(object, m_paintInfo, m_filter, m_savedInfo.context);
424 m_filter = 0;
425 }
426
427 // Restore context & repaint rect
428 m_paintInfo.context->restore();
429 m_paintInfo.rect = m_savedInfo.rect;
430 }
431
chunkSetupFillCallbackWebCore::SVGRootInlineBoxPaintWalker432 bool chunkSetupFillCallback(InlineBox* box)
433 {
434 InlineFlowBox* flowBox = box->parent();
435
436 // Setup fill paint server
437 RenderObject* object = flowBox->renderer();
438 ASSERT(object);
439
440 ASSERT(!m_strokePaintServer);
441 teardownFillPaintServer();
442
443 m_fillPaintServer = SVGPaintServer::fillPaintServer(object->style(), object);
444 if (m_fillPaintServer) {
445 m_fillPaintServer->setup(m_paintInfo.context, object, ApplyToFillTargetType, true);
446 m_fillPaintServerObject = object;
447 return true;
448 }
449
450 return false;
451 }
452
chunkSetupStrokeCallbackWebCore::SVGRootInlineBoxPaintWalker453 bool chunkSetupStrokeCallback(InlineBox* box)
454 {
455 InlineFlowBox* flowBox = box->parent();
456
457 // Setup stroke paint server
458 RenderObject* object = flowBox->renderer();
459 ASSERT(object);
460
461 // If we're both stroked & filled, teardown fill paint server before stroking.
462 teardownFillPaintServer();
463 teardownStrokePaintServer();
464
465 m_strokePaintServer = SVGPaintServer::strokePaintServer(object->style(), object);
466
467 if (m_strokePaintServer) {
468 m_strokePaintServer->setup(m_paintInfo.context, object, ApplyToStrokeTargetType, true);
469 m_strokePaintServerObject = object;
470 return true;
471 }
472
473 return false;
474 }
475
chunkPortionCallbackWebCore::SVGRootInlineBoxPaintWalker476 void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const TransformationMatrix& chunkCtm,
477 const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end)
478 {
479 RenderText* text = textBox->textRenderer();
480 ASSERT(text);
481
482 RenderStyle* styleToUse = text->style(textBox->isFirstLineStyle());
483 ASSERT(styleToUse);
484
485 startOffset += textBox->start();
486
487 int textDecorations = styleToUse->textDecorationsInEffect();
488
489 int textWidth = 0;
490 IntPoint decorationOrigin;
491 SVGTextDecorationInfo info;
492
493 if (!chunkCtm.isIdentity())
494 m_paintInfo.context->concatCTM(chunkCtm);
495
496 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
497 if (it->isHidden())
498 continue;
499
500 // Determine how many characters - starting from the current - can be drawn at once.
501 Vector<SVGChar>::iterator itSearch = it + 1;
502 while (itSearch != end) {
503 if (itSearch->drawnSeperated || itSearch->isHidden())
504 break;
505
506 itSearch++;
507 }
508
509 const UChar* stringStart = text->characters() + startOffset + (it - start);
510 unsigned int stringLength = itSearch - it;
511
512 // Paint decorations, that have to be drawn before the text gets drawn
513 if (textDecorations != TDNONE && m_paintInfo.phase != PaintPhaseSelection) {
514 textWidth = styleToUse->font().width(svgTextRunForInlineTextBox(stringStart, stringLength, styleToUse, textBox, (*it).x));
515 decorationOrigin = IntPoint((int) (*it).x, (int) (*it).y - styleToUse->font().ascent());
516 info = m_rootBox->retrievePaintServersForTextDecoration(text);
517 }
518
519 if (textDecorations & UNDERLINE && textWidth != 0.0f)
520 textBox->paintDecoration(UNDERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);
521
522 if (textDecorations & OVERLINE && textWidth != 0.0f)
523 textBox->paintDecoration(OVERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);
524
525 // Paint text
526 SVGPaintServer* activePaintServer = m_fillPaintServer;
527 if (!activePaintServer)
528 activePaintServer = m_strokePaintServer;
529
530 ASSERT(activePaintServer);
531 textBox->paintCharacters(m_paintInfo, m_tx, m_ty, *it, stringStart, stringLength, activePaintServer);
532
533 // Paint decorations, that have to be drawn afterwards
534 if (textDecorations & LINE_THROUGH && textWidth != 0.0f)
535 textBox->paintDecoration(LINE_THROUGH, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);
536
537 // Skip processed characters
538 it = itSearch - 1;
539 }
540
541 if (!chunkCtm.isIdentity())
542 m_paintInfo.context->concatCTM(chunkCtm.inverse());
543 }
544
545 private:
546 SVGRootInlineBox* m_rootBox;
547 bool m_chunkStarted : 1;
548
549 RenderObject::PaintInfo m_paintInfo;
550 RenderObject::PaintInfo m_savedInfo;
551
552 FloatRect m_boundingBox;
553 SVGResourceFilter* m_filter;
554 SVGResourceFilter* m_rootFilter;
555
556 SVGPaintServer* m_fillPaintServer;
557 SVGPaintServer* m_strokePaintServer;
558
559 RenderObject* m_fillPaintServerObject;
560 RenderObject* m_strokePaintServerObject;
561
562 int m_tx;
563 int m_ty;
564 };
565
paint(RenderObject::PaintInfo & paintInfo,int tx,int ty)566 void SVGRootInlineBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty)
567 {
568 if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground)
569 return;
570
571 RenderObject::PaintInfo savedInfo(paintInfo);
572 paintInfo.context->save();
573
574 SVGResourceFilter* filter = 0;
575 FloatRect boundingBox(tx + x(), ty + y(), width(), height());
576
577 // Initialize text rendering
578 SVGRenderBase::prepareToRenderSVGContent(renderer(), paintInfo, boundingBox, filter);
579
580 // Render text, chunk-by-chunk
581 SVGRootInlineBoxPaintWalker walkerCallback(this, filter, paintInfo, tx, ty);
582 SVGTextChunkWalker<SVGRootInlineBoxPaintWalker> walker(&walkerCallback,
583 &SVGRootInlineBoxPaintWalker::chunkPortionCallback,
584 &SVGRootInlineBoxPaintWalker::chunkStartCallback,
585 &SVGRootInlineBoxPaintWalker::chunkEndCallback,
586 &SVGRootInlineBoxPaintWalker::chunkSetupFillCallback,
587 &SVGRootInlineBoxPaintWalker::chunkSetupStrokeCallback);
588
589 walkTextChunks(&walker);
590
591 // Finalize text rendering
592 SVGRenderBase::finishRenderSVGContent(renderer(), paintInfo, filter, savedInfo.context);
593 paintInfo.context->restore();
594 }
595
placeBoxesHorizontally(int,int & leftPosition,int & rightPosition,bool &)596 int SVGRootInlineBox::placeBoxesHorizontally(int, int& leftPosition, int& rightPosition, bool&)
597 {
598 // Remove any offsets caused by RTL text layout
599 leftPosition = 0;
600 rightPosition = 0;
601 return 0;
602 }
603
verticallyAlignBoxes(int)604 int SVGRootInlineBox::verticallyAlignBoxes(int)
605 {
606 // height is set by layoutInlineBoxes.
607 return height();
608 }
609
cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange & range)610 float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range)
611 {
612 ASSERT(!range.isOpen());
613 ASSERT(range.isClosed());
614 ASSERT(range.box->isInlineTextBox());
615
616 InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box);
617 RenderText* text = textBox->textRenderer();
618 RenderStyle* style = text->style();
619
620 return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox, 0));
621 }
622
cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange & range)623 float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range)
624 {
625 ASSERT(!range.isOpen());
626 ASSERT(range.isClosed());
627 ASSERT(range.box->isInlineTextBox());
628
629 InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box);
630 RenderText* text = textBox->textRenderer();
631 const Font& font = text->style()->font();
632
633 return (range.endOffset - range.startOffset) * (font.ascent() + font.descent());
634 }
635
svgTextRunForInlineTextBox(const UChar * c,int len,RenderStyle * style,const InlineTextBox * textBox,float xPos)636 TextRun svgTextRunForInlineTextBox(const UChar* c, int len, RenderStyle* style, const InlineTextBox* textBox, float xPos)
637 {
638 ASSERT(textBox);
639 ASSERT(style);
640
641 TextRun run(c, len, false, static_cast<int>(xPos), textBox->toAdd(), textBox->direction() == RTL, textBox->m_dirOverride || style->visuallyOrdered());
642
643 #if ENABLE(SVG_FONTS)
644 run.setReferencingRenderObject(textBox->textRenderer()->parent());
645 #endif
646
647 // We handle letter & word spacing ourselves
648 run.disableSpacing();
649 return run;
650 }
651
cummulatedWidthOrHeightOfTextChunk(SVGTextChunk & chunk,bool calcWidthOnly)652 static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk& chunk, bool calcWidthOnly)
653 {
654 float length = 0.0f;
655 Vector<SVGChar>::iterator charIt = chunk.start;
656
657 Vector<SVGInlineBoxCharacterRange>::iterator it = chunk.boxes.begin();
658 Vector<SVGInlineBoxCharacterRange>::iterator end = chunk.boxes.end();
659
660 for (; it != end; ++it) {
661 SVGInlineBoxCharacterRange& range = *it;
662
663 SVGInlineTextBox* box = static_cast<SVGInlineTextBox*>(range.box);
664 RenderStyle* style = box->renderer()->style();
665
666 for (int i = range.startOffset; i < range.endOffset; ++i) {
667 ASSERT(charIt <= chunk.end);
668
669 // Determine how many characters - starting from the current - can be measured at once.
670 // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width
671 // of a string is not the sum of the boundaries of all contained glyphs.
672 Vector<SVGChar>::iterator itSearch = charIt + 1;
673 Vector<SVGChar>::iterator endSearch = charIt + range.endOffset - i;
674 while (itSearch != endSearch) {
675 // No need to check for 'isHidden()' here as this function is not called for text paths.
676 if (itSearch->drawnSeperated)
677 break;
678
679 itSearch++;
680 }
681
682 unsigned int positionOffset = itSearch - charIt;
683
684 // Calculate width/height of subrange
685 SVGInlineBoxCharacterRange subRange;
686 subRange.box = range.box;
687 subRange.startOffset = i;
688 subRange.endOffset = i + positionOffset;
689
690 if (calcWidthOnly)
691 length += cummulatedWidthOfInlineBoxCharacterRange(subRange);
692 else
693 length += cummulatedHeightOfInlineBoxCharacterRange(subRange);
694
695 // Calculate gap between the previous & current range
696 // <text x="10 50 70">ABCD</text> - we need to take the gaps between A & B into account
697 // so add "40" as width, and analogous for B & C, add "20" as width.
698 if (itSearch > chunk.start && itSearch < chunk.end) {
699 SVGChar& lastCharacter = *(itSearch - 1);
700 SVGChar& currentCharacter = *itSearch;
701
702 int offset = box->direction() == RTL ? box->end() - i - positionOffset + 1 : box->start() + i + positionOffset - 1;
703
704 // FIXME: does this need to change to handle multichar glyphs?
705 int charsConsumed = 1;
706 String glyphName;
707 if (calcWidthOnly) {
708 float lastGlyphWidth = box->calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName);
709 length += currentCharacter.x - lastCharacter.x - lastGlyphWidth;
710 } else {
711 float lastGlyphHeight = box->calculateGlyphHeight(style, offset, 0);
712 length += currentCharacter.y - lastCharacter.y - lastGlyphHeight;
713 }
714 }
715
716 // Advance processed characters
717 i += positionOffset - 1;
718 charIt = itSearch;
719 }
720 }
721
722 ASSERT(charIt == chunk.end);
723 return length;
724 }
725
cummulatedWidthOfTextChunk(SVGTextChunk & chunk)726 static float cummulatedWidthOfTextChunk(SVGTextChunk& chunk)
727 {
728 return cummulatedWidthOrHeightOfTextChunk(chunk, true);
729 }
730
cummulatedHeightOfTextChunk(SVGTextChunk & chunk)731 static float cummulatedHeightOfTextChunk(SVGTextChunk& chunk)
732 {
733 return cummulatedWidthOrHeightOfTextChunk(chunk, false);
734 }
735
calculateTextAnchorShiftForTextChunk(SVGTextChunk & chunk,ETextAnchor anchor)736 static float calculateTextAnchorShiftForTextChunk(SVGTextChunk& chunk, ETextAnchor anchor)
737 {
738 float shift = 0.0f;
739
740 if (chunk.isVerticalText)
741 shift = cummulatedHeightOfTextChunk(chunk);
742 else
743 shift = cummulatedWidthOfTextChunk(chunk);
744
745 if (anchor == TA_MIDDLE)
746 shift *= -0.5f;
747 else
748 shift *= -1.0f;
749
750 return shift;
751 }
752
applyTextAnchorToTextChunk(SVGTextChunk & chunk)753 static void applyTextAnchorToTextChunk(SVGTextChunk& chunk)
754 {
755 // This method is not called for chunks containing chars aligned on a path.
756 // -> all characters are visible, no need to check for "isHidden()" anywhere.
757
758 if (chunk.anchor == TA_START)
759 return;
760
761 float shift = calculateTextAnchorShiftForTextChunk(chunk, chunk.anchor);
762
763 // Apply correction to chunk
764 Vector<SVGChar>::iterator chunkIt = chunk.start;
765 for (; chunkIt != chunk.end; ++chunkIt) {
766 SVGChar& curChar = *chunkIt;
767
768 if (chunk.isVerticalText)
769 curChar.y += shift;
770 else
771 curChar.x += shift;
772 }
773
774 // Move inline boxes
775 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin();
776 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end();
777
778 for (; boxIt != boxEnd; ++boxIt) {
779 SVGInlineBoxCharacterRange& range = *boxIt;
780
781 InlineBox* curBox = range.box;
782 ASSERT(curBox->isInlineTextBox());
783
784 // Move target box
785 if (chunk.isVerticalText)
786 curBox->setY(curBox->y() + static_cast<int>(shift));
787 else
788 curBox->setX(curBox->x() + static_cast<int>(shift));
789 }
790 }
791
calculateTextLengthCorrectionForTextChunk(SVGTextChunk & chunk,ELengthAdjust lengthAdjust,float & computedLength)792 static float calculateTextLengthCorrectionForTextChunk(SVGTextChunk& chunk, ELengthAdjust lengthAdjust, float& computedLength)
793 {
794 if (chunk.textLength <= 0.0f)
795 return 0.0f;
796
797 float computedWidth = cummulatedWidthOfTextChunk(chunk);
798 float computedHeight = cummulatedHeightOfTextChunk(chunk);
799
800 if ((computedWidth <= 0.0f && !chunk.isVerticalText) ||
801 (computedHeight <= 0.0f && chunk.isVerticalText))
802 return 0.0f;
803
804 if (chunk.isVerticalText)
805 computedLength = computedHeight;
806 else
807 computedLength = computedWidth;
808
809 if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) {
810 if (chunk.isVerticalText)
811 chunk.ctm.scaleNonUniform(1.0f, chunk.textLength / computedLength);
812 else
813 chunk.ctm.scaleNonUniform(chunk.textLength / computedLength, 1.0f);
814
815 return 0.0f;
816 }
817
818 return (chunk.textLength - computedLength) / float(chunk.end - chunk.start);
819 }
820
applyTextLengthCorrectionToTextChunk(SVGTextChunk & chunk)821 static void applyTextLengthCorrectionToTextChunk(SVGTextChunk& chunk)
822 {
823 // This method is not called for chunks containing chars aligned on a path.
824 // -> all characters are visible, no need to check for "isHidden()" anywhere.
825
826 // lengthAdjust="spacingAndGlyphs" is handled by modifying chunk.ctm
827 float computedLength = 0.0f;
828 float spacingToApply = calculateTextLengthCorrectionForTextChunk(chunk, chunk.lengthAdjust, computedLength);
829
830 if (!chunk.ctm.isIdentity() && chunk.lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) {
831 SVGChar& firstChar = *(chunk.start);
832
833 // Assure we apply the chunk scaling in the right origin
834 TransformationMatrix newChunkCtm;
835 newChunkCtm.translate(firstChar.x, firstChar.y);
836 newChunkCtm = chunk.ctm * newChunkCtm;
837 newChunkCtm.translate(-firstChar.x, -firstChar.y);
838
839 chunk.ctm = newChunkCtm;
840 }
841
842 // Apply correction to chunk
843 if (spacingToApply != 0.0f) {
844 Vector<SVGChar>::iterator chunkIt = chunk.start;
845 for (; chunkIt != chunk.end; ++chunkIt) {
846 SVGChar& curChar = *chunkIt;
847 curChar.drawnSeperated = true;
848
849 if (chunk.isVerticalText)
850 curChar.y += (chunkIt - chunk.start) * spacingToApply;
851 else
852 curChar.x += (chunkIt - chunk.start) * spacingToApply;
853 }
854 }
855 }
856
computePerCharacterLayoutInformation()857 void SVGRootInlineBox::computePerCharacterLayoutInformation()
858 {
859 // Clean up any previous layout information
860 m_svgChars.clear();
861 m_svgTextChunks.clear();
862
863 // Build layout information for all contained render objects
864 SVGCharacterLayoutInfo info(m_svgChars);
865 buildLayoutInformation(this, info);
866
867 // Now all layout information are available for every character
868 // contained in any of our child inline/flow boxes. Build list
869 // of text chunks now, to be able to apply text-anchor shifts.
870 buildTextChunks(m_svgChars, m_svgTextChunks, this);
871
872 // Layout all text chunks
873 // text-anchor needs to be applied to individual chunks.
874 layoutTextChunks();
875
876 // Finally the top left position of our box is known.
877 // Propogate this knownledge to our RenderSVGText parent.
878 FloatPoint topLeft = topLeftPositionOfCharacterRange(m_svgChars);
879 block()->setLocation((int) floorf(topLeft.x()), (int) floorf(topLeft.y()));
880
881 // Layout all InlineText/Flow boxes
882 // BEWARE: This requires the root top/left position to be set correctly before!
883 layoutInlineBoxes();
884 }
885
buildLayoutInformation(InlineFlowBox * start,SVGCharacterLayoutInfo & info)886 void SVGRootInlineBox::buildLayoutInformation(InlineFlowBox* start, SVGCharacterLayoutInfo& info)
887 {
888 if (start->isRootInlineBox()) {
889 ASSERT(start->renderer()->node()->hasTagName(SVGNames::textTag));
890
891 SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(start->renderer()->node());
892 ASSERT(positioningElement);
893 ASSERT(positioningElement->parentNode());
894
895 info.addLayoutInformation(positioningElement);
896 }
897
898 LastGlyphInfo lastGlyph;
899
900 for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
901 if (curr->renderer()->isText())
902 buildLayoutInformationForTextBox(info, static_cast<InlineTextBox*>(curr), lastGlyph);
903 else {
904 ASSERT(curr->isInlineFlowBox());
905 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr);
906
907 if (!flowBox->renderer()->node())
908 continue; // Skip generated content.
909
910 bool isAnchor = flowBox->renderer()->node()->hasTagName(SVGNames::aTag);
911 bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag);
912
913 if (!isTextPath && !isAnchor) {
914 SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(flowBox->renderer()->node());
915 ASSERT(positioningElement);
916 ASSERT(positioningElement->parentNode());
917
918 info.addLayoutInformation(positioningElement);
919 } else if (!isAnchor) {
920 info.setInPathLayout(true);
921
922 // Handle text-anchor/textLength on path, which is special.
923 SVGTextContentElement* textContent = 0;
924 Node* node = flowBox->renderer()->node();
925 if (node && node->isSVGElement())
926 textContent = static_cast<SVGTextContentElement*>(node);
927 ASSERT(textContent);
928
929 ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();
930 ETextAnchor anchor = flowBox->renderer()->style()->svgStyle()->textAnchor();
931 float textAnchorStartOffset = 0.0f;
932
933 // Initialize sub-layout. We need to create text chunks from the textPath
934 // children using our standard layout code, to be able to measure the
935 // text length using our normal methods and not textPath specific hacks.
936 Vector<SVGChar> tempChars;
937 Vector<SVGTextChunk> tempChunks;
938
939 SVGCharacterLayoutInfo tempInfo(tempChars);
940 buildLayoutInformation(flowBox, tempInfo);
941
942 buildTextChunks(tempChars, tempChunks, flowBox);
943
944 Vector<SVGTextChunk>::iterator it = tempChunks.begin();
945 Vector<SVGTextChunk>::iterator end = tempChunks.end();
946
947 TransformationMatrix ctm;
948 float computedLength = 0.0f;
949
950 for (; it != end; ++it) {
951 SVGTextChunk& chunk = *it;
952
953 // Apply text-length calculation
954 info.pathExtraAdvance += calculateTextLengthCorrectionForTextChunk(chunk, lengthAdjust, computedLength);
955
956 if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) {
957 info.pathTextLength += computedLength;
958 info.pathChunkLength += chunk.textLength;
959 }
960
961 // Calculate text-anchor start offset
962 if (anchor == TA_START)
963 continue;
964
965 textAnchorStartOffset += calculateTextAnchorShiftForTextChunk(chunk, anchor);
966 }
967
968 info.addLayoutInformation(flowBox, textAnchorStartOffset);
969 }
970
971 float shiftxSaved = info.shiftx;
972 float shiftySaved = info.shifty;
973
974 buildLayoutInformation(flowBox, info);
975 info.processedChunk(shiftxSaved, shiftySaved);
976
977 if (isTextPath)
978 info.setInPathLayout(false);
979 }
980 }
981 }
982
layoutInlineBoxes()983 void SVGRootInlineBox::layoutInlineBoxes()
984 {
985 int lowX = INT_MAX;
986 int lowY = INT_MAX;
987 int highX = INT_MIN;
988 int highY = INT_MIN;
989
990 // Layout all child boxes
991 Vector<SVGChar>::iterator it = m_svgChars.begin();
992 layoutInlineBoxes(this, it, lowX, highX, lowY, highY);
993 ASSERT(it == m_svgChars.end());
994 }
995
layoutInlineBoxes(InlineFlowBox * start,Vector<SVGChar>::iterator & it,int & lowX,int & highX,int & lowY,int & highY)996 void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox* start, Vector<SVGChar>::iterator& it, int& lowX, int& highX, int& lowY, int& highY)
997 {
998 for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
999 RenderStyle* style = curr->renderer()->style();
1000 if (curr->renderer()->isText()) {
1001 SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(curr);
1002 unsigned length = textBox->len();
1003
1004 SVGChar curChar = *it;
1005 ASSERT(it != m_svgChars.end());
1006
1007 FloatRect stringRect;
1008 for (unsigned i = 0; i < length; ++i) {
1009 ASSERT(it != m_svgChars.end());
1010
1011 if (it->isHidden()) {
1012 ++it;
1013 continue;
1014 }
1015
1016 stringRect.unite(textBox->calculateGlyphBoundaries(style, textBox->start() + i, *it));
1017 ++it;
1018 }
1019
1020 IntRect enclosedStringRect = enclosingIntRect(stringRect);
1021
1022 int minX = enclosedStringRect.x();
1023 int maxX = minX + enclosedStringRect.width();
1024
1025 int minY = enclosedStringRect.y();
1026 int maxY = minY + enclosedStringRect.height();
1027
1028 curr->setX(minX - block()->x());
1029 curr->setWidth(enclosedStringRect.width());
1030
1031 curr->setY(minY - block()->y());
1032 textBox->setHeight(enclosedStringRect.height());
1033
1034 if (minX < lowX)
1035 lowX = minX;
1036
1037 if (maxX > highX)
1038 highX = maxX;
1039
1040 if (minY < lowY)
1041 lowY = minY;
1042
1043 if (maxY > highY)
1044 highY = maxY;
1045 } else {
1046 ASSERT(curr->isInlineFlowBox());
1047
1048 int minX = INT_MAX;
1049 int minY = INT_MAX;
1050 int maxX = INT_MIN;
1051 int maxY = INT_MIN;
1052
1053 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr);
1054
1055 if (!flowBox->renderer()->node())
1056 continue; // Skip generated content.
1057
1058 layoutInlineBoxes(flowBox, it, minX, maxX, minY, maxY);
1059
1060 curr->setX(minX - block()->x());
1061 curr->setWidth(maxX - minX);
1062
1063 curr->setY(minY - block()->y());
1064 static_cast<SVGInlineFlowBox*>(curr)->setHeight(maxY - minY);
1065
1066 if (minX < lowX)
1067 lowX = minX;
1068
1069 if (maxX > highX)
1070 highX = maxX;
1071
1072 if (minY < lowY)
1073 lowY = minY;
1074
1075 if (maxY > highY)
1076 highY = maxY;
1077 }
1078 }
1079
1080 if (start->isSVGRootInlineBox()) {
1081 int top = lowY - block()->y();
1082 int bottom = highY - block()->y();
1083
1084 start->setX(lowX - block()->x());
1085 start->setY(top);
1086
1087 start->setWidth(highX - lowX);
1088 static_cast<SVGRootInlineBox*>(start)->setHeight(highY - lowY);
1089
1090 start->setVerticalOverflowPositions(top, bottom);
1091 start->setVerticalSelectionPositions(top, bottom);
1092 }
1093 }
1094
buildLayoutInformationForTextBox(SVGCharacterLayoutInfo & info,InlineTextBox * textBox,LastGlyphInfo & lastGlyph)1095 void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo& info, InlineTextBox* textBox, LastGlyphInfo& lastGlyph)
1096 {
1097 RenderText* text = textBox->textRenderer();
1098 ASSERT(text);
1099
1100 RenderStyle* style = text->style(textBox->isFirstLineStyle());
1101 ASSERT(style);
1102
1103 const Font& font = style->font();
1104 SVGInlineTextBox* svgTextBox = static_cast<SVGInlineTextBox*>(textBox);
1105
1106 unsigned length = textBox->len();
1107
1108 const SVGRenderStyle* svgStyle = style->svgStyle();
1109 bool isVerticalText = isVerticalWritingMode(svgStyle);
1110
1111 int charsConsumed = 0;
1112 for (unsigned i = 0; i < length; i += charsConsumed) {
1113 SVGChar svgChar;
1114
1115 if (info.inPathLayout())
1116 svgChar.pathData = SVGCharOnPath::create();
1117
1118 float glyphWidth = 0.0f;
1119 float glyphHeight = 0.0f;
1120
1121 int extraCharsAvailable = length - i - 1;
1122
1123 String unicodeStr;
1124 String glyphName;
1125 if (textBox->direction() == RTL) {
1126 glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->end() - i, extraCharsAvailable, charsConsumed, glyphName);
1127 glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->end() - i, extraCharsAvailable);
1128 unicodeStr = String(textBox->textRenderer()->text()->characters() + textBox->end() - i, charsConsumed);
1129 } else {
1130 glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->start() + i, extraCharsAvailable, charsConsumed, glyphName);
1131 glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->start() + i, extraCharsAvailable);
1132 unicodeStr = String(textBox->textRenderer()->text()->characters() + textBox->start() + i, charsConsumed);
1133 }
1134
1135 bool assignedX = false;
1136 bool assignedY = false;
1137
1138 if (info.xValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && !isVerticalText))) {
1139 if (!isVerticalText)
1140 svgChar.newTextChunk = true;
1141
1142 assignedX = true;
1143 svgChar.drawnSeperated = true;
1144 info.curx = info.xValueNext();
1145 }
1146
1147 if (info.yValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && isVerticalText))) {
1148 if (isVerticalText)
1149 svgChar.newTextChunk = true;
1150
1151 assignedY = true;
1152 svgChar.drawnSeperated = true;
1153 info.cury = info.yValueNext();
1154 }
1155
1156 float dx = 0.0f;
1157 float dy = 0.0f;
1158
1159 // Apply x-axis shift
1160 if (info.dxValueAvailable()) {
1161 svgChar.drawnSeperated = true;
1162
1163 dx = info.dxValueNext();
1164 info.dx += dx;
1165
1166 if (!info.inPathLayout())
1167 info.curx += dx;
1168 }
1169
1170 // Apply y-axis shift
1171 if (info.dyValueAvailable()) {
1172 svgChar.drawnSeperated = true;
1173
1174 dy = info.dyValueNext();
1175 info.dy += dy;
1176
1177 if (!info.inPathLayout())
1178 info.cury += dy;
1179 }
1180
1181 // Take letter & word spacing and kerning into account
1182 float spacing = font.letterSpacing() + calculateKerning(textBox->renderer()->node()->renderer());
1183
1184 const UChar* currentCharacter = text->characters() + (textBox->direction() == RTL ? textBox->end() - i : textBox->start() + i);
1185 const UChar* lastCharacter = 0;
1186
1187 if (textBox->direction() == RTL) {
1188 if (i < textBox->end())
1189 lastCharacter = text->characters() + textBox->end() - i + 1;
1190 } else {
1191 if (i > 0)
1192 lastCharacter = text->characters() + textBox->start() + i - 1;
1193 }
1194
1195 if (info.nextDrawnSeperated || spacing != 0.0f) {
1196 info.nextDrawnSeperated = false;
1197 svgChar.drawnSeperated = true;
1198 }
1199
1200 if (currentCharacter && Font::treatAsSpace(*currentCharacter) && lastCharacter && !Font::treatAsSpace(*lastCharacter)) {
1201 spacing += font.wordSpacing();
1202
1203 if (spacing != 0.0f && !info.inPathLayout())
1204 info.nextDrawnSeperated = true;
1205 }
1206
1207 float orientationAngle = glyphOrientationToAngle(svgStyle, isVerticalText, *currentCharacter);
1208
1209 float xOrientationShift = 0.0f;
1210 float yOrientationShift = 0.0f;
1211 float glyphAdvance = calculateGlyphAdvanceAndShiftRespectingOrientation(isVerticalText, orientationAngle, glyphWidth, glyphHeight,
1212 font, svgChar, xOrientationShift, yOrientationShift);
1213
1214 // Handle textPath layout mode
1215 if (info.inPathLayout()) {
1216 float extraAdvance = isVerticalText ? dy : dx;
1217 float newOffset = FLT_MIN;
1218
1219 if (assignedX && !isVerticalText)
1220 newOffset = info.curx;
1221 else if (assignedY && isVerticalText)
1222 newOffset = info.cury;
1223
1224 float correctedGlyphAdvance = glyphAdvance;
1225
1226 // Handle lengthAdjust="spacingAndGlyphs" by specifying per-character scale operations
1227 if (info.pathTextLength > 0.0f && info.pathChunkLength > 0.0f) {
1228 if (isVerticalText) {
1229 svgChar.pathData->yScale = info.pathChunkLength / info.pathTextLength;
1230 spacing *= svgChar.pathData->yScale;
1231 correctedGlyphAdvance *= svgChar.pathData->yScale;
1232 } else {
1233 svgChar.pathData->xScale = info.pathChunkLength / info.pathTextLength;
1234 spacing *= svgChar.pathData->xScale;
1235 correctedGlyphAdvance *= svgChar.pathData->xScale;
1236 }
1237 }
1238
1239 // Handle letter & word spacing on text path
1240 float pathExtraAdvance = info.pathExtraAdvance;
1241 info.pathExtraAdvance += spacing;
1242
1243 svgChar.pathData->hidden = !info.nextPathLayoutPointAndAngle(correctedGlyphAdvance, extraAdvance, newOffset);
1244 svgChar.drawnSeperated = true;
1245
1246 info.pathExtraAdvance = pathExtraAdvance;
1247 }
1248
1249 // Apply rotation
1250 if (info.angleValueAvailable())
1251 info.angle = info.angleValueNext();
1252
1253 // Apply baseline-shift
1254 if (info.baselineShiftValueAvailable()) {
1255 svgChar.drawnSeperated = true;
1256 float shift = info.baselineShiftValueNext();
1257
1258 if (isVerticalText)
1259 info.shiftx += shift;
1260 else
1261 info.shifty -= shift;
1262 }
1263
1264 // Take dominant-baseline / alignment-baseline into account
1265 yOrientationShift += alignmentBaselineToShift(isVerticalText, text, font);
1266
1267 svgChar.x = info.curx;
1268 svgChar.y = info.cury;
1269 svgChar.angle = info.angle;
1270
1271 // For text paths any shift (dx/dy/baseline-shift) has to be applied after the rotation
1272 if (!info.inPathLayout()) {
1273 svgChar.x += info.shiftx + xOrientationShift;
1274 svgChar.y += info.shifty + yOrientationShift;
1275
1276 if (orientationAngle != 0.0f)
1277 svgChar.angle += orientationAngle;
1278
1279 if (svgChar.angle != 0.0f)
1280 svgChar.drawnSeperated = true;
1281 } else {
1282 svgChar.pathData->orientationAngle = orientationAngle;
1283
1284 if (isVerticalText)
1285 svgChar.angle -= 90.0f;
1286
1287 svgChar.pathData->xShift = info.shiftx + xOrientationShift;
1288 svgChar.pathData->yShift = info.shifty + yOrientationShift;
1289
1290 // Translate to glyph midpoint
1291 if (isVerticalText) {
1292 svgChar.pathData->xShift += info.dx;
1293 svgChar.pathData->yShift -= glyphAdvance / 2.0f;
1294 } else {
1295 svgChar.pathData->xShift -= glyphAdvance / 2.0f;
1296 svgChar.pathData->yShift += info.dy;
1297 }
1298 }
1299
1300 double kerning = 0.0;
1301 #if ENABLE(SVG_FONTS)
1302 SVGFontElement* svgFont = 0;
1303 if (style->font().isSVGFont())
1304 svgFont = style->font().svgFont();
1305
1306 if (lastGlyph.isValid && style->font().isSVGFont()) {
1307 SVGHorizontalKerningPair kerningPair;
1308 if (svgFont->getHorizontalKerningPairForStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeStr, glyphName, kerningPair))
1309 kerning = kerningPair.kerning;
1310 }
1311
1312 if (style->font().isSVGFont()) {
1313 lastGlyph.unicode = unicodeStr;
1314 lastGlyph.glyphName = glyphName;
1315 lastGlyph.isValid = true;
1316 } else
1317 lastGlyph.isValid = false;
1318 #endif
1319
1320 svgChar.x -= (float)kerning;
1321
1322 // Advance to new position
1323 if (isVerticalText) {
1324 svgChar.drawnSeperated = true;
1325 info.cury += glyphAdvance + spacing;
1326 } else
1327 info.curx += glyphAdvance + spacing - (float)kerning;
1328
1329 // Advance to next character group
1330 for (int k = 0; k < charsConsumed; ++k) {
1331 info.svgChars.append(svgChar);
1332 info.processedSingleCharacter();
1333 svgChar.drawnSeperated = false;
1334 svgChar.newTextChunk = false;
1335 }
1336 }
1337 }
1338
buildTextChunks(Vector<SVGChar> & svgChars,Vector<SVGTextChunk> & svgTextChunks,InlineFlowBox * start)1339 void SVGRootInlineBox::buildTextChunks(Vector<SVGChar>& svgChars, Vector<SVGTextChunk>& svgTextChunks, InlineFlowBox* start)
1340 {
1341 SVGTextChunkLayoutInfo info(svgTextChunks);
1342 info.it = svgChars.begin();
1343 info.chunk.start = svgChars.begin();
1344 info.chunk.end = svgChars.begin();
1345
1346 buildTextChunks(svgChars, start, info);
1347 ASSERT(info.it == svgChars.end());
1348 }
1349
buildTextChunks(Vector<SVGChar> & svgChars,InlineFlowBox * start,SVGTextChunkLayoutInfo & info)1350 void SVGRootInlineBox::buildTextChunks(Vector<SVGChar>& svgChars, InlineFlowBox* start, SVGTextChunkLayoutInfo& info)
1351 {
1352 #if DEBUG_CHUNK_BUILDING > 1
1353 fprintf(stderr, " -> buildTextChunks(start=%p)\n", start);
1354 #endif
1355
1356 for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
1357 if (curr->renderer()->isText()) {
1358 InlineTextBox* textBox = static_cast<InlineTextBox*>(curr);
1359
1360 unsigned length = textBox->len();
1361 if (!length)
1362 continue;
1363
1364 #if DEBUG_CHUNK_BUILDING > 1
1365 fprintf(stderr, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n",
1366 textBox, length, textBox->start(), textBox->end(), (int) info.handlingTextPath);
1367 #endif
1368
1369 RenderText* text = textBox->textRenderer();
1370 ASSERT(text);
1371 ASSERT(text->node());
1372
1373 SVGTextContentElement* textContent = 0;
1374 Node* node = text->node()->parent();
1375 while (node && node->isSVGElement() && !textContent) {
1376 if (static_cast<SVGElement*>(node)->isTextContent())
1377 textContent = static_cast<SVGTextContentElement*>(node);
1378 else
1379 node = node->parentNode();
1380 }
1381 ASSERT(textContent);
1382
1383 // Start new character range for the first chunk
1384 bool isFirstCharacter = info.svgTextChunks.isEmpty() && info.chunk.start == info.it && info.chunk.start == info.chunk.end;
1385 if (isFirstCharacter) {
1386 ASSERT(info.chunk.boxes.isEmpty());
1387 info.chunk.boxes.append(SVGInlineBoxCharacterRange());
1388 } else
1389 ASSERT(!info.chunk.boxes.isEmpty());
1390
1391 // Walk string to find out new chunk positions, if existant
1392 for (unsigned i = 0; i < length; ++i) {
1393 ASSERT(info.it != svgChars.end());
1394
1395 SVGInlineBoxCharacterRange& range = info.chunk.boxes.last();
1396 if (range.isOpen()) {
1397 range.box = curr;
1398 range.startOffset = (i == 0 ? 0 : i - 1);
1399
1400 #if DEBUG_CHUNK_BUILDING > 1
1401 fprintf(stderr, " | -> Range is open! box=%p, startOffset=%i\n", range.box, range.startOffset);
1402 #endif
1403 }
1404
1405 // If a new (or the first) chunk has been started, record it's text-anchor and writing mode.
1406 if (info.assignChunkProperties) {
1407 info.assignChunkProperties = false;
1408
1409 info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle());
1410 info.chunk.isTextPath = info.handlingTextPath;
1411 info.chunk.anchor = text->style()->svgStyle()->textAnchor();
1412 info.chunk.textLength = textContent->textLength().value(textContent);
1413 info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();
1414
1415 #if DEBUG_CHUNK_BUILDING > 1
1416 fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", info.chunk.isVerticalText, info.chunk.anchor);
1417 #endif
1418 }
1419
1420 if (i > 0 && !isFirstCharacter && (*info.it).newTextChunk) {
1421 // Close mid chunk & character range
1422 ASSERT(!range.isOpen());
1423 ASSERT(!range.isClosed());
1424
1425 range.endOffset = i;
1426 closeTextChunk(info);
1427
1428 #if DEBUG_CHUNK_BUILDING > 1
1429 fprintf(stderr, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range.endOffset);
1430 #endif
1431
1432 // Prepare for next chunk, if we're not at the end
1433 startTextChunk(info);
1434 if (i + 1 == length) {
1435 #if DEBUG_CHUNK_BUILDING > 1
1436 fprintf(stderr, " | -> Record last chunk of inline text box!\n");
1437 #endif
1438
1439 startTextChunk(info);
1440 SVGInlineBoxCharacterRange& range = info.chunk.boxes.last();
1441
1442 info.assignChunkProperties = false;
1443 info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle());
1444 info.chunk.isTextPath = info.handlingTextPath;
1445 info.chunk.anchor = text->style()->svgStyle()->textAnchor();
1446 info.chunk.textLength = textContent->textLength().value(textContent);
1447 info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();
1448
1449 range.box = curr;
1450 range.startOffset = i;
1451
1452 ASSERT(!range.isOpen());
1453 ASSERT(!range.isClosed());
1454 }
1455 }
1456
1457 // This should only hold true for the first character of the first chunk
1458 if (isFirstCharacter)
1459 isFirstCharacter = false;
1460
1461 ++info.it;
1462 }
1463
1464 #if DEBUG_CHUNK_BUILDING > 1
1465 fprintf(stderr, " -> Finished inline text box!\n");
1466 #endif
1467
1468 SVGInlineBoxCharacterRange& range = info.chunk.boxes.last();
1469 if (!range.isOpen() && !range.isClosed()) {
1470 #if DEBUG_CHUNK_BUILDING > 1
1471 fprintf(stderr, " -> Last range not closed - closing with endOffset: %i\n", length);
1472 #endif
1473
1474 // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk.
1475 range.endOffset = length;
1476
1477 if (info.it != svgChars.end()) {
1478 #if DEBUG_CHUNK_BUILDING > 1
1479 fprintf(stderr, " -> Not at last character yet!\n");
1480 #endif
1481
1482 // If we're not at the end of the last box to be processed, and if the next
1483 // character starts a new chunk, then close the current chunk and start a new one.
1484 if ((*info.it).newTextChunk) {
1485 #if DEBUG_CHUNK_BUILDING > 1
1486 fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n");
1487 #endif
1488
1489 closeTextChunk(info);
1490 startTextChunk(info);
1491 } else {
1492 // Just start a new character range
1493 info.chunk.boxes.append(SVGInlineBoxCharacterRange());
1494
1495 #if DEBUG_CHUNK_BUILDING > 1
1496 fprintf(stderr, " -> Next character does NOT start a new chunk! Starting new character range...\n");
1497 #endif
1498 }
1499 } else {
1500 #if DEBUG_CHUNK_BUILDING > 1
1501 fprintf(stderr, " -> Closing final chunk! Finished processing!\n");
1502 #endif
1503
1504 // Close final chunk, once we're at the end of the last box
1505 closeTextChunk(info);
1506 }
1507 }
1508 } else {
1509 ASSERT(curr->isInlineFlowBox());
1510 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr);
1511
1512 if (!flowBox->renderer()->node())
1513 continue; // Skip generated content.
1514
1515 bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag);
1516
1517 #if DEBUG_CHUNK_BUILDING > 1
1518 fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath);
1519 #endif
1520
1521 if (isTextPath)
1522 info.handlingTextPath = true;
1523
1524 buildTextChunks(svgChars, flowBox, info);
1525
1526 if (isTextPath)
1527 info.handlingTextPath = false;
1528 }
1529 }
1530
1531 #if DEBUG_CHUNK_BUILDING > 1
1532 fprintf(stderr, " <- buildTextChunks(start=%p)\n", start);
1533 #endif
1534 }
1535
svgTextChunks() const1536 const Vector<SVGTextChunk>& SVGRootInlineBox::svgTextChunks() const
1537 {
1538 return m_svgTextChunks;
1539 }
1540
layoutTextChunks()1541 void SVGRootInlineBox::layoutTextChunks()
1542 {
1543 Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin();
1544 Vector<SVGTextChunk>::iterator end = m_svgTextChunks.end();
1545
1546 for (; it != end; ++it) {
1547 SVGTextChunk& chunk = *it;
1548
1549 #if DEBUG_CHUNK_BUILDING > 0
1550 {
1551 fprintf(stderr, "Handle TEXT CHUNK! anchor=%i, textLength=%f, lengthAdjust=%i, isVerticalText=%i, isTextPath=%i start=%p, end=%p -> dist: %i\n",
1552 (int) chunk.anchor, chunk.textLength, (int) chunk.lengthAdjust, (int) chunk.isVerticalText,
1553 (int) chunk.isTextPath, chunk.start, chunk.end, (unsigned int) (chunk.end - chunk.start));
1554
1555 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin();
1556 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end();
1557
1558 unsigned int i = 0;
1559 for (; boxIt != boxEnd; ++boxIt) {
1560 SVGInlineBoxCharacterRange& range = *boxIt; i++;
1561 fprintf(stderr, " -> RANGE %i STARTOFFSET: %i, ENDOFFSET: %i, BOX: %p\n", i, range.startOffset, range.endOffset, range.box);
1562 }
1563 }
1564 #endif
1565
1566 if (chunk.isTextPath)
1567 continue;
1568
1569 // text-path & textLength, with lengthAdjust="spacing" is already handled for textPath layouts.
1570 applyTextLengthCorrectionToTextChunk(chunk);
1571
1572 // text-anchor is already handled for textPath layouts.
1573 applyTextAnchorToTextChunk(chunk);
1574 }
1575 }
1576
addPaintServerToTextDecorationInfo(ETextDecoration decoration,SVGTextDecorationInfo & info,RenderObject * object)1577 static inline void addPaintServerToTextDecorationInfo(ETextDecoration decoration, SVGTextDecorationInfo& info, RenderObject* object)
1578 {
1579 if (object->style()->svgStyle()->hasFill())
1580 info.fillServerMap.set(decoration, object);
1581
1582 if (object->style()->svgStyle()->hasStroke())
1583 info.strokeServerMap.set(decoration, object);
1584 }
1585
retrievePaintServersForTextDecoration(RenderObject * start)1586 SVGTextDecorationInfo SVGRootInlineBox::retrievePaintServersForTextDecoration(RenderObject* start)
1587 {
1588 ASSERT(start);
1589
1590 Vector<RenderObject*> parentChain;
1591 while ((start = start->parent())) {
1592 parentChain.prepend(start);
1593
1594 // Stop at our direct <text> parent.
1595 if (start->isSVGText())
1596 break;
1597 }
1598
1599 Vector<RenderObject*>::iterator it = parentChain.begin();
1600 Vector<RenderObject*>::iterator end = parentChain.end();
1601
1602 SVGTextDecorationInfo info;
1603
1604 for (; it != end; ++it) {
1605 RenderObject* object = *it;
1606 ASSERT(object);
1607
1608 RenderStyle* style = object->style();
1609 ASSERT(style);
1610
1611 int decorations = style->textDecoration();
1612 if (decorations != NONE) {
1613 if (decorations & OVERLINE)
1614 addPaintServerToTextDecorationInfo(OVERLINE, info, object);
1615
1616 if (decorations & UNDERLINE)
1617 addPaintServerToTextDecorationInfo(UNDERLINE, info, object);
1618
1619 if (decorations & LINE_THROUGH)
1620 addPaintServerToTextDecorationInfo(LINE_THROUGH, info, object);
1621 }
1622 }
1623
1624 return info;
1625 }
1626
walkTextChunks(SVGTextChunkWalkerBase * walker,const SVGInlineTextBox * textBox)1627 void SVGRootInlineBox::walkTextChunks(SVGTextChunkWalkerBase* walker, const SVGInlineTextBox* textBox)
1628 {
1629 ASSERT(walker);
1630
1631 Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin();
1632 Vector<SVGTextChunk>::iterator itEnd = m_svgTextChunks.end();
1633
1634 for (; it != itEnd; ++it) {
1635 SVGTextChunk& curChunk = *it;
1636
1637 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = curChunk.boxes.begin();
1638 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = curChunk.boxes.end();
1639
1640 InlineBox* lastNotifiedBox = 0;
1641 InlineBox* prevBox = 0;
1642
1643 unsigned int chunkOffset = 0;
1644 bool startedFirstChunk = false;
1645
1646 for (; boxIt != boxEnd; ++boxIt) {
1647 SVGInlineBoxCharacterRange& range = *boxIt;
1648
1649 ASSERT(range.box->isInlineTextBox());
1650 SVGInlineTextBox* rangeTextBox = static_cast<SVGInlineTextBox*>(range.box);
1651
1652 if (textBox && rangeTextBox != textBox) {
1653 chunkOffset += range.endOffset - range.startOffset;
1654 continue;
1655 }
1656
1657 // Eventually notify that we started a new chunk
1658 if (!textBox && !startedFirstChunk) {
1659 startedFirstChunk = true;
1660
1661 lastNotifiedBox = range.box;
1662 walker->start(range.box);
1663 } else {
1664 // Eventually apply new style, as this chunk spans multiple boxes (with possible different styling)
1665 if (prevBox && prevBox != range.box) {
1666 lastNotifiedBox = range.box;
1667
1668 walker->end(prevBox);
1669 walker->start(lastNotifiedBox);
1670 }
1671 }
1672
1673 unsigned int length = range.endOffset - range.startOffset;
1674
1675 Vector<SVGChar>::iterator itCharBegin = curChunk.start + chunkOffset;
1676 Vector<SVGChar>::iterator itCharEnd = curChunk.start + chunkOffset + length;
1677 ASSERT(itCharEnd <= curChunk.end);
1678
1679 // Process this chunk portion
1680 if (textBox)
1681 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd);
1682 else {
1683 if (walker->setupFill(range.box))
1684 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd);
1685
1686 if (walker->setupStroke(range.box))
1687 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd);
1688 }
1689
1690 chunkOffset += length;
1691
1692 if (!textBox)
1693 prevBox = range.box;
1694 }
1695
1696 if (!textBox && startedFirstChunk)
1697 walker->end(lastNotifiedBox);
1698 }
1699 }
1700
1701 } // namespace WebCore
1702
1703 #endif // ENABLE(SVG)
1704