KHtml

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

KDE's Doxygen guidelines are available online.