KHtml

SVGInlineTextBox.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 2007 Rob Buis <[email protected]>
5  * (C) 2007 Nikolas Zimmermann <[email protected]>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB. If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23 
24 #include "wtf/Platform.h"
25 
26 #if ENABLE(SVG)
27 #include "SVGInlineTextBox.h"
28 
29 #include "Document.h"
30 /*#include "Editor.h"
31 #include "Frame.h"
32 #include "GraphicsContext.h"
33 #include "InlineFlowBox.h"
34 #include "Range.h"*/
35 #include "SVGPaintServer.h"
36 #include "SVGRootInlineBox.h"
37 /*#include "Text.h"*/
38 #include "render_line.h"
39 
40 #include <float.h>
41 #include <math.h>
42 
43 using std::max;
44 
45 namespace WebCore
46 {
47 
48 SVGInlineTextBox::SVGInlineTextBox(RenderObject *obj)
49  : InlineTextBox(obj)
50 {
51 }
52 
53 int SVGInlineTextBox::selectionTop()
54 {
55  return m_y;
56 }
57 
58 int SVGInlineTextBox::selectionHeight()
59 {
60  return m_height;
61 }
62 
63 SVGRootInlineBox *SVGInlineTextBox::svgRootInlineBox() const
64 {
65  // qCDebug(KHTML_LOG) << "find inline box";
66  // Find associated root inline box
67  InlineFlowBox *parentBox = parent();
68 
69  while (parentBox && !parentBox->isRootInlineBox()) {
70  parentBox = parentBox->parent();
71  }
72 
73  ASSERT(parentBox);
74  ASSERT(parentBox->isRootInlineBox());
75 
76  if (!parentBox->isSVGRootInlineBox()) {
77  return nullptr;
78  }
79 
80  return static_cast<SVGRootInlineBox *>(parentBox);
81 }
82 
83 float SVGInlineTextBox::calculateGlyphWidth(RenderStyle *style, int offset, int extraCharsAvailable, int &charsConsumed, String &glyphName) const
84 {
85  ASSERT(style);
86  return style->htmlFont().floatWidth(renderText()->text(), offset, 1, extraCharsAvailable, charsConsumed, glyphName);
87  //return style->htmlFont().floatWidth(svgTextRunForInlineTextBox(renderText()->text() + offset, 1, style, this, 0), extraCharsAvailable, charsConsumed, glyphName);
88 }
89 
90 float SVGInlineTextBox::calculateGlyphHeight(RenderStyle *style, int offset, int extraCharsAvailable) const
91 {
92  Q_UNUSED(offset);
93  Q_UNUSED(extraCharsAvailable);
94  ASSERT(style);
95 
96  // This is just a guess, and the only purpose of this function is to centralize this hack.
97  // In real-life top-top-bottom scripts this won't be enough, I fear.
98  return style->htmlFont().ascent() + style->htmlFont().descent();
99 }
100 
101 FloatRect SVGInlineTextBox::calculateGlyphBoundaries(RenderStyle *style, int offset, const SVGChar &svgChar) const
102 {
103  const Font &font = style->htmlFont();
104 
105  // Take RTL text into account and pick right glyph width/height.
106  float glyphWidth = 0.0f;
107 
108  // FIXME: account for multi-character glyphs
109  int charsConsumed;
110  String glyphName;
111  if (!m_reversed) {
112  glyphWidth = calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName);
113  } else {
114  glyphWidth = calculateGlyphWidth(style, start() + end() - offset, 0, charsConsumed, glyphName);
115  }
116 
117  float x1 = svgChar.x;
118  float x2 = svgChar.x + glyphWidth;
119 
120  float y1 = svgChar.y - font.ascent();
121  float y2 = svgChar.y + font.descent();
122 
123  FloatRect glyphRect(x1, y1, x2 - x1, y2 - y1);
124 
125  // Take per-character transformations into account
126  AffineTransform ctm = svgChar.characterTransform();
127  if (!ctm.isIdentity()) {
128  glyphRect = ctm.mapRect(glyphRect);
129  }
130 
131  return glyphRect;
132 }
133 
134 // Helper class for closestCharacterToPosition()
135 struct SVGInlineTextBoxClosestCharacterToPositionWalker {
136  SVGInlineTextBoxClosestCharacterToPositionWalker(int x, int y)
137  : m_character(nullptr)
138  , m_distance(FLT_MAX)
139  , m_x(x)
140  , m_y(y)
141  , m_offset(0)
142  {
143  }
144 
145  void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm,
146  const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end)
147  {
148  RenderStyle *style = textBox->renderText()->style();
149 
150  Vector<SVGChar>::iterator closestCharacter = nullptr;
151  unsigned int closestOffset = UINT_MAX;
152 
153  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
154  if (it->isHidden()) {
155  continue;
156  }
157 
158  unsigned int newOffset = textBox->start() + (it - start) + startOffset;
159  FloatRect glyphRect = chunkCtm.mapRect(textBox->calculateGlyphBoundaries(style, newOffset, *it));
160 
161  // Take RTL text into account and pick right glyph width/height.
162  // NOTE: This offset has to be corrected _after_ calling calculateGlyphBoundaries
163  if (textBox->m_reversed) {
164  newOffset = textBox->start() + textBox->end() - newOffset;
165  }
166 
167  // Calculate distances relative to the glyph mid-point. I hope this is accurate enough.
168  float xDistance = glyphRect.x() + glyphRect.width() / 2.0f - m_x;
169  float yDistance = glyphRect.y() - glyphRect.height() / 2.0f - m_y;
170 
171  float newDistance = sqrtf(xDistance * xDistance + yDistance * yDistance);
172  if (newDistance <= m_distance) {
173  m_distance = newDistance;
174  closestOffset = newOffset;
175  closestCharacter = it;
176  }
177  }
178 
179  if (closestOffset != UINT_MAX) {
180  // Record current chunk, if it contains the current closest character next to the mouse.
181  m_character = closestCharacter;
182  m_offset = closestOffset;
183  }
184  }
185 
186  SVGChar *character() const
187  {
188  return m_character;
189  }
190 
191  int offset() const
192  {
193  if (!m_character) {
194  return 0;
195  }
196 
197  return m_offset;
198  }
199 
200 private:
201  Vector<SVGChar>::iterator m_character;
202  float m_distance;
203 
204  int m_x;
205  int m_y;
206  int m_offset;
207 };
208 
209 // Helper class for selectionRect()
210 struct SVGInlineTextBoxSelectionRectWalker {
211  SVGInlineTextBoxSelectionRectWalker()
212  {
213  }
214 
215  void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm,
216  const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end)
217  {
218  RenderStyle *style = textBox->renderText()->style();
219 
220  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
221  if (it->isHidden()) {
222  continue;
223  }
224 
225  unsigned int newOffset = textBox->start() + (it - start) + startOffset;
226  m_selectionRect.unite(textBox->calculateGlyphBoundaries(style, newOffset, *it));
227  }
228 
229  m_selectionRect = chunkCtm.mapRect(m_selectionRect);
230  }
231 
232  FloatRect selectionRect() const
233  {
234  return m_selectionRect;
235  }
236 
237 private:
238  FloatRect m_selectionRect;
239 };
240 
241 SVGChar *SVGInlineTextBox::closestCharacterToPosition(int x, int y, int &offset) const
242 {
243  SVGRootInlineBox *rootBox = svgRootInlineBox();
244  if (!rootBox) {
245  return nullptr;
246  }
247 
248  SVGInlineTextBoxClosestCharacterToPositionWalker walkerCallback(x, y);
249  SVGTextChunkWalker<SVGInlineTextBoxClosestCharacterToPositionWalker> walker(&walkerCallback, &SVGInlineTextBoxClosestCharacterToPositionWalker::chunkPortionCallback);
250 
251  rootBox->walkTextChunks(&walker, this);
252 
253  offset = walkerCallback.offset();
254  return walkerCallback.character();
255 }
256 
257 bool SVGInlineTextBox::svgCharacterHitsPosition(int x, int y, int &offset) const
258 {
259  SVGChar *charAtPosPtr = closestCharacterToPosition(x, y, offset);
260  if (!charAtPosPtr) {
261  return false;
262  }
263 
264  SVGChar &charAtPos = *charAtPosPtr;
265  RenderStyle *style = renderText()->style(m_firstLine);
266  FloatRect glyphRect = calculateGlyphBoundaries(style, offset, charAtPos);
267 
268  if (m_reversed) {
269  offset++;
270  }
271 
272  // FIXME: todo list
273  // (#13910) This code does not handle bottom-to-top/top-to-bottom vertical text.
274 
275  // Check whether y position hits the current character
276  if (y < charAtPos.y - glyphRect.height() || y > charAtPos.y) {
277  return false;
278  }
279 
280  // Check whether x position hits the current character
281  if (x < charAtPos.x) {
282  if (offset > 0 && !m_reversed) {
283  return true;
284  } else if (offset < (int) end() && m_reversed) {
285  return true;
286  }
287 
288  return false;
289  }
290 
291  // If we are past the last glyph of this box, don't mark it as 'hit' anymore.
292  if (x >= charAtPos.x + glyphRect.width() && offset == (int) end()) {
293  return false;
294  }
295 
296  // Snap to character at half of it's advance
297  if (x >= charAtPos.x + glyphRect.width() / 2.0) {
298  offset += m_reversed ? -1 : 1;
299  }
300 
301  return true;
302 }
303 
304 int SVGInlineTextBox::offsetForPosition(int x, bool includePartialGlyphs) const
305 {
306  Q_UNUSED(x);
307  Q_UNUSED(includePartialGlyphs);
308  // SVG doesn't use the offset <-> position selection system.
309  ASSERT_NOT_REACHED();
310  return 0;
311 }
312 
313 int SVGInlineTextBox::positionForOffset(int offset) const
314 {
315  Q_UNUSED(offset);
316  // SVG doesn't use the offset <-> position selection system.
317  ASSERT_NOT_REACHED();
318  return 0;
319 }
320 
321 /*bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty)
322 {
323  ASSERT(!isLineBreak());
324 
325  IntRect rect = selectionRect(0, 0, 0, len());
326  if (object()->style()->visibility() == VISIBLE && rect.contains(x, y)) {
327  object()->updateHitTestResult(result, IntPoint(x - tx, y - ty));
328  return true;
329  }
330 
331  return false;
332 }*/
333 
334 IntRect SVGInlineTextBox::selectionRect(int, int, int startPos, int endPos)
335 {
336  if (startPos >= endPos) {
337  return IntRect();
338  }
339 
340  // TODO: Actually respect startPos/endPos - we're returning the _full_ selectionRect
341  // here. This won't lead to visible bugs, but to extra work being done. Investigate.
342  SVGRootInlineBox *rootBox = svgRootInlineBox();
343  if (!rootBox) {
344  return IntRect();
345  }
346 
347  SVGInlineTextBoxSelectionRectWalker walkerCallback;
348  SVGTextChunkWalker<SVGInlineTextBoxSelectionRectWalker> walker(&walkerCallback, &SVGInlineTextBoxSelectionRectWalker::chunkPortionCallback);
349 
350  rootBox->walkTextChunks(&walker, this);
351  return enclosingIntRect(walkerCallback.selectionRect());
352 }
353 
354 void SVGInlineTextBox::paintCharacters(RenderObject::PaintInfo &paintInfo, int tx, int ty, const SVGChar &svgChar, const UChar *chars, int length, SVGPaintServer *activePaintServer)
355 {
356  Q_UNUSED(tx);
357  Q_UNUSED(ty);
358  Q_UNUSED(chars);
359  Q_UNUSED(length);
360  Q_UNUSED(activePaintServer);
361  // qCDebug(KHTML_LOG) << "paint character";
362  /*FIXME khtml if (object()->style()->visibility() != VISIBLE || paintInfo.phase == PaintPhaseOutline)
363  return;
364 
365  ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);*/
366 
367  RenderText *text = renderText();
368  ASSERT(text);
369 
370  // Determine whether or not we're selected.
371  bool haveSelection = text->selectionState() != RenderObject::SelectionNone;
372  if (!haveSelection && paintInfo.phase == PaintActionSelection)
373  // When only painting the selection, don't bother to paint if there is none.
374  {
375  return;
376  }
377 
378  // Determine whether or not we have a composition.
379  /*bool containsComposition = text->document()->frame()->editor()->compositionNode() == text->node();
380  bool useCustomUnderlines = containsComposition && text->document()->frame()->editor()->compositionUsesCustomUnderlines();*/
381 
382  // Set our font
383  RenderStyle *styleToUse = text->style(m_firstLine);
384  const Font *font = &styleToUse->htmlFont();
385  if (styleToUse->font() != paintInfo.p->font()) {
386  paintInfo.p->setFont(styleToUse->font());
387  }
388 
389  AffineTransform ctm = svgChar.characterTransform();
390  if (!ctm.isIdentity()) {
391  paintInfo.p->setWorldMatrix(ctm, true);
392  }
393  //paintInfo.context->concatCTM(ctm);
394 
395  // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
396  // and marked text.
397  if (true/*paintInfo.phase != PaintPhaseSelection && !isPrinting*/) {
398 #if PLATFORM(MAC)
399  // Custom highlighters go behind everything else.
400  if (styleToUse->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) {
401  paintCustomHighlight(tx, ty, styleToUse->highlight());
402  }
403 #endif
404 
405  /*FIXME khtml if (containsComposition && !useCustomUnderlines)
406  paintCompositionBackground(paintInfo.context, tx, ty, styleToUse, font,
407  text->document()->frame()->editor()->compositionStart(),
408  text->document()->frame()->editor()->compositionEnd());
409 
410  paintDocumentMarkers(paintInfo.context, tx, ty, styleToUse, font, true);*/
411 
412  /*if (haveSelection && !useCustomUnderlines) {
413  int boxStartOffset = chars - text->characters() - start();
414  //FIXME khtml paintSelection(boxStartOffset, svgChar, chars, length, paintInfo.context, styleToUse, font);
415  }*/
416  }
417 
418  // Set a text shadow if we have one.
419  // FIXME: Support multiple shadow effects. Need more from the CG API before
420  // we can do this.
421  //bool setShadow = false;
422  if (styleToUse->textShadow()) {
423  /*FIXME khtml paintInfo.context->setShadow(IntSize(styleToUse->textShadow()->x, styleToUse->textShadow()->y),
424  styleToUse->textShadow()->blur, styleToUse->textShadow()->color);
425  setShadow = true;*/
426  }
427 
428  IntPoint origin((int) svgChar.x, (int) svgChar.y);
429  // qCDebug(KHTML_LOG) << "origin: " << svgChar.x << svgChar.y;
430  //TextRun run = svgTextRunForInlineTextBox(chars, length, styleToUse, this, svgChar.x);
431 
432 #if ENABLE(SVG_FONTS)
433  // SVG Fonts need access to the paint server used to draw the current text chunk.
434  // They need to be able to call renderPath() on a SVGPaintServer object.
435  //FIXME khtml run.setActivePaintServer(activePaintServer);
436 #endif
437 
438  //FIXME khtml paintInfo.context->drawText(run, origin);
439  // qCDebug(KHTML_LOG) << "font size:" << font->getFontDef().size;
440  // qCDebug(KHTML_LOG) << "text:" << QString::fromRawData(renderText()->string()->s + m_start, m_len);
441 
442  font->drawText(paintInfo.p, svgChar.x, svgChar.y, renderText()->string()->s, renderText()->string()->l, m_start, m_len,
443  m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight);
444 
445  if (true/*paintInfo.phase != PaintPhaseSelection*/) {
446  //FIXME khtml paintDocumentMarkers(paintInfo.context, tx, ty, styleToUse, font, false);
447 
448  /*if (useCustomUnderlines) {
449  const Vector<CompositionUnderline>& underlines = text->document()->frame()->editor()->customCompositionUnderlines();
450  size_t numUnderlines = underlines.size();
451 
452  for (size_t index = 0; index < numUnderlines; ++index) {
453  const CompositionUnderline& underline = underlines[index];
454 
455  if (underline.endOffset <= start())
456  // underline is completely before this run. This might be an underline that sits
457  // before the first run we draw, or underlines that were within runs we skipped
458  // due to truncation.
459  continue;
460 
461  if (underline.startOffset <= end()) {
462  // underline intersects this run. Paint it.
463  //FIXME khtml paintCompositionUnderline(paintInfo.context, tx, ty, underline);
464  if (underline.endOffset > end() + 1)
465  // underline also runs into the next run. Bail now, no more marker advancement.
466  break;
467  } else
468  // underline is completely after this run, bail. A later run will paint it.
469  break;
470  }
471  }*/
472 
473  }
474 
475  /*if (setShadow)
476  paintInfo.context->clearShadow();*/
477 
478  if (!ctm.isIdentity()) {
479  paintInfo.p->setWorldMatrix(ctm.inverse(), true);
480  }
481  //paintInfo.context->concatCTM(ctm.inverse());
482 }
483 
484 void SVGInlineTextBox::paintSelection(int boxStartOffset, const SVGChar &svgChar, const UChar *chars, int length, khtml::RenderObject::PaintInfo &p, RenderStyle *style, const Font *f)
485 {
486  Q_UNUSED(boxStartOffset);
487  Q_UNUSED(svgChar);
488  Q_UNUSED(chars);
489  Q_UNUSED(length);
490  Q_UNUSED(p);
491  Q_UNUSED(style);
492  Q_UNUSED(f);
493  /*if (selectionState() == RenderObject::SelectionNone)
494  return;
495 
496  int startPos, endPos;
497  selectionStartEnd(startPos, endPos);
498 
499  if (startPos >= endPos)
500  return;
501 
502  Color textColor = style->color();
503  Color color = object()->selectionBackgroundColor();
504  if (!color.isValid() || color.alpha() == 0)
505  return;
506 
507  // If the text color ends up being the same as the selection background, invert the selection
508  // background. This should basically never happen, since the selection has transparency.
509  if (textColor == color)
510  color = Color(0xff - color.red(), 0xff - color.green(), 0xff - color.blue());
511 
512  // Map from text box positions and a given start offset to chunk positions
513  // 'boxStartOffset' represents the beginning of the text chunk.
514  if ((startPos > boxStartOffset && endPos > boxStartOffset + length) || boxStartOffset >= endPos)
515  return;
516 
517  if (endPos > boxStartOffset + length)
518  endPos = boxStartOffset + length;
519 
520  if (startPos < boxStartOffset)
521  startPos = boxStartOffset;
522 
523  ASSERT(startPos >= boxStartOffset);
524  ASSERT(endPos <= boxStartOffset + length);
525  ASSERT(startPos < endPos);
526 
527  p->save();
528 
529  int adjust = startPos >= boxStartOffset ? boxStartOffset : 0;
530  p->drawHighlightForText(svgTextRunForInlineTextBox(textObject()->text()->characters() + start() + boxStartOffset, length, style, this, svgChar.x),
531  IntPoint((int) svgChar.x, (int) svgChar.y - f->ascent()),
532  f->ascent() + f->descent(), color, startPos - adjust, endPos - adjust);
533 
534  p->restore();*/
535 }
536 
537 static inline Path pathForDecoration(ETextDecoration decoration, RenderObject *object, float x, float y, float width)
538 {
539  Q_UNUSED(decoration);
540 
541  float thickness = SVGRenderStyle::cssPrimitiveToLength(object, object->style()->svgStyle()->strokeWidth(), 1.0f);
542 
543  const Font &font = object->style()->htmlFont();
544  thickness = max(thickness * powf(font.getFontDef().size, 2.0f) / font.unitsPerEm(), 1.0f);
545 
546  if (decoration == UNDERLINE) {
547  y += thickness * 1.5f; // For compatibility with Batik/Opera
548  } else if (decoration == OVERLINE) {
549  y += thickness;
550  }
551 
552  float halfThickness = thickness / 2.0f;
553  return Path::createRectangle(FloatRect(x + halfThickness, y, width - 2.0f * halfThickness, thickness));
554 }
555 
556 void SVGInlineTextBox::paintDecoration(ETextDecoration decoration, khtml::RenderObject::PaintInfo &pI, int tx, int ty, int width, const SVGChar &svgChar, const SVGTextDecorationInfo &info)
557 {
558  Q_UNUSED(decoration);
559  Q_UNUSED(pI);
560  Q_UNUSED(tx);
561  Q_UNUSED(ty);
562  Q_UNUSED(width);
563  Q_UNUSED(svgChar);
564  Q_UNUSED(info);
565 
566  /*FIXME if (object()->style()->visibility() != VISIBLE)
567  return;
568 
569  // This function does NOT accept combinated text decorations. It's meant to be invoked for just one.
570  ASSERT(decoration == TDNONE || decoration == UNDERLINE || decoration == OVERLINE || decoration == LINE_THROUGH || decoration == BLINK);
571 
572  bool isFilled = info.fillServerMap.contains(decoration);
573  bool isStroked = info.strokeServerMap.contains(decoration);
574 
575  if (!isFilled && !isStroked)
576  return;
577 
578  if (decoration == UNDERLINE)
579  ty += m_baseline;
580  else if (decoration == LINE_THROUGH)
581  ty += 2 * m_baseline / 3;
582 
583  context->save();
584  context->beginPath();
585 
586  AffineTransform ctm = svgChar.characterTransform();
587  if (!ctm.isIdentity())
588  context->concatCTM(ctm);
589 
590  if (isFilled) {
591  if (RenderObject* fillObject = info.fillServerMap.get(decoration)) {
592  if (SVGPaintServer* fillPaintServer = SVGPaintServer::fillPaintServer(fillObject->style(), fillObject)) {
593  context->addPath(pathForDecoration(decoration, fillObject, tx, ty, width));
594  fillPaintServer->draw(context, fillObject, ApplyToFillTargetType);
595  }
596  }
597  }
598 
599  if (isStroked) {
600  if (RenderObject* strokeObject = info.strokeServerMap.get(decoration)) {
601  if (SVGPaintServer* strokePaintServer = SVGPaintServer::strokePaintServer(strokeObject->style(), strokeObject)) {
602  context->addPath(pathForDecoration(decoration, strokeObject, tx, ty, width));
603  strokePaintServer->draw(context, strokeObject, ApplyToStrokeTargetType);
604  }
605  }
606  }
607 
608  context->restore();*/
609 }
610 
611 } // namespace WebCore
612 
613 #endif
RightToLeft
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 22:48:22 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.