KHtml

render_text.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999-2003 Lars Knoll ([email protected])
5  * (C) 2000-2003 Dirk Mueller ([email protected])
6  * (C) 2003, 2006 Apple Computer, Inc.
7  * (C) 2004-2005 Allan Sandfeld Jensen ([email protected])
8  * (C) 2008 Germain Garand ([email protected])
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB. If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  *
25  */
26 
27 //#define DEBUG_LAYOUT
28 //#define BIDI_DEBUG
29 
30 #include "render_text.h"
31 #include "render_canvas.h"
32 #include "break_lines.h"
33 #include "render_arena.h"
34 #include "rendering/render_position.h"
35 #include <xml/dom_nodeimpl.h>
36 
37 #include <misc/loader.h>
38 #include <misc/helper.h>
39 
40 #include <QBitmap>
41 #include <QImage>
42 #include <QPainter>
43 #include "khtml_debug.h"
44 #include <limits.h>
45 #include <math.h>
46 
47 #include <config-khtml.h>
48 
49 #if HAVE_ALLOCA_H
50 // explicitly included for systems that don't provide it in stdlib.h or malloc.h
51 # include <alloca.h>
52 #else
53 # if HAVE_MALLOC_H
54 # include <malloc.h>
55 # else
56 # include <stdlib.h>
57 # endif
58 #endif
59 
60 using namespace khtml;
61 using namespace DOM;
62 
63 #ifndef NDEBUG
64 static bool inInlineTextBoxDetach;
65 #endif
66 
67 void InlineTextBox::detach(RenderArena *renderArena, bool noRemove)
68 {
69  if (!noRemove) {
70  remove();
71  }
72 
73 #ifndef NDEBUG
74  inInlineTextBoxDetach = true;
75 #endif
76  delete this;
77 #ifndef NDEBUG
78  inInlineTextBoxDetach = false;
79 #endif
80 
81  // Recover the size left there for us by operator delete and free the memory.
82  renderArena->free(*(size_t *)this, this);
83 }
84 
85 void *InlineTextBox::operator new(size_t sz, RenderArena *renderArena) throw()
86 {
87  return renderArena->allocate(sz);
88 }
89 
90 void InlineTextBox::operator delete(void *ptr, size_t sz)
91 {
92  assert(inInlineTextBoxDetach);
93 
94 #ifdef KHTML_USE_ARENA_ALLOCATOR
95  // Stash size where detach can find it.
96  *(size_t *)ptr = sz;
97 #endif
98 }
99 
100 void InlineTextBox::selectionStartEnd(int &sPos, int &ePos)
101 {
102  int startPos, endPos;
103  if (object()->selectionState() == RenderObject::SelectionInside) {
104  startPos = 0;
105  endPos = renderText()->string()->l;
106  } else {
107  renderText()->selectionStartEnd(startPos, endPos);
108  if (object()->selectionState() == RenderObject::SelectionStart) {
109  endPos = renderText()->string()->l;
110  } else if (object()->selectionState() == RenderObject::SelectionEnd) {
111  startPos = 0;
112  }
113  }
114 
115  sPos = qMax(startPos - m_start, 0);
116  ePos = qMin(endPos - m_start, (int)m_len);
117 }
118 
119 RenderObject::SelectionState InlineTextBox::selectionState()
120 {
121  RenderObject::SelectionState state = object()->selectionState();
122  if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd ||
123  state == RenderObject::SelectionBoth) {
124  int startPos, endPos;
125  renderText()->selectionStartEnd(startPos, endPos);
126 
127  bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
128  bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= m_start + m_len);
129  if (start && end) {
130  state = RenderObject::SelectionBoth;
131  } else if (start) {
132  state = RenderObject::SelectionStart;
133  } else if (end) {
134  state = RenderObject::SelectionEnd;
135  } else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
136  (state == RenderObject::SelectionStart || endPos > m_start + m_len)) {
137  state = RenderObject::SelectionInside;
138  }
139  }
140  return state;
141 }
142 
143 void InlineTextBox::paint(RenderObject::PaintInfo &i, int tx, int ty)
144 {
145  if (object()->isBR() || object()->style()->visibility() != VISIBLE ||
146  m_truncation == cFullTruncation || i.phase == PaintActionOutline) {
147  return;
148  }
149 
150  if (i.phase == PaintActionSelection && object()->selectionState() == RenderObject::SelectionNone)
151  // When only painting the selection, don't bother to paint if there is none.
152  {
153  return;
154  }
155 
156  int xPos = tx + m_x;
157  int w = width();
158  if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x())) {
159  return;
160  }
161 
162  // Set our font.
163  RenderStyle *styleToUse = object()->style(m_firstLine);
164  int d = styleToUse->textDecorationsInEffect();
165  if (styleToUse->font() != i.p->font()) {
166  i.p->setFont(styleToUse->font());
167  }
168  const Font *font = &styleToUse->htmlFont();
169  bool haveSelection = selectionState() != RenderObject::SelectionNone;
170 
171  // Now calculate startPos and endPos, for painting selection.
172  // We paint selection while endPos > 0
173  int ePos = 0, sPos = 0;
174  if (haveSelection && !object()->canvas()->staticMode()) {
175  selectionStartEnd(sPos, ePos);
176  }
177  i.p->setPen(styleToUse->color());
178 
179  if (m_len > 0 && i.phase != PaintActionSelection) {
180  int endPoint = m_len;
181  if (m_truncation != cNoTruncation) {
182  endPoint = m_truncation - m_start;
183  }
184  if (styleToUse->textShadow()) {
185  paintShadow(i.p, font, tx, ty, styleToUse->textShadow());
186  }
187  if (!haveSelection || sPos != 0 || ePos != m_len) {
188  font->drawText(i.p, m_x + tx, m_y + ty + m_baseline, renderText()->string()->s, renderText()->string()->l, m_start, endPoint,
189  m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight);
190  }
191  }
192 
193  if (d != TDNONE && i.phase != PaintActionSelection && styleToUse->htmlHacks()) {
194  i.p->setPen(styleToUse->color());
195  paintDecoration(i.p, font, tx, ty, d);
196  }
197 
198  if (haveSelection && i.phase == PaintActionSelection) {
199  //qCDebug(KHTML_LOG) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos;
200  if (sPos < ePos) {
201  paintSelection(font, renderText(), i.p, styleToUse, tx, ty, sPos, ePos, d);
202  }
203  }
204 }
205 
206 /** returns the proper ::selection pseudo style for the given element
207  * @return the style or 0 if no ::selection pseudo applies.
208  */
209 inline const RenderStyle *retrieveSelectionPseudoStyle(const RenderObject *obj)
210 {
211  // https://www.w3.org/Style/CSS/Test/CSS3/Selectors/20021129/html/tests/css3-modsel-162.html
212  // is of the opinion that ::selection of parent elements is also to be applied
213  // to children, so let's do it.
214  while (obj) {
215  const RenderStyle *style = obj->style()->getPseudoStyle(RenderStyle::SELECTION);
216  if (style) {
217  return style;
218  }
219 
220  obj = obj->parent();
221  }/*wend*/
222  return nullptr;
223 }
224 
225 void InlineTextBox::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle *style, int tx, int ty, int startPos, int endPos, int deco)
226 {
227  if (startPos > m_len) {
228  return;
229  }
230  if (startPos < 0) {
231  startPos = 0;
232  }
233 
234  QColor hc;
235  QColor hbg;
236  const RenderStyle *pseudoStyle = retrieveSelectionPseudoStyle(text);
237  if (pseudoStyle) {
238  // ### support outline (mandated by CSS3)
239  // ### support background-image? (optional by CSS3)
240  if (pseudoStyle->backgroundColor().isValid()) {
241  hbg = pseudoStyle->backgroundColor();
242  }
243  hc = pseudoStyle->color();
244  } else {
245  hc = style->palette().color(QPalette::Active, QPalette::HighlightedText);
246  hbg = style->palette().color(QPalette::Active, QPalette::Highlight);
247  // ### should be at most retrieved once per render text
249  // It may happen that the contrast is -- well -- virtually non existent.
250  // In this case, simply swap the colors, thus in compliance with
251  // NN4 (win32 only), IE, and Mozilla.
252  if (!khtml::hasSufficientContrast(hbg, bg)) {
253  qSwap(hc, hbg);
254  }
255  }
256 
257  p->setPen(hc);
258 
259  //qCDebug(KHTML_LOG) << "textRun::painting(" << QString::fromRawData(text->str->s + m_start, m_len).left(30) << ") at(" << m_x+tx << "/" << m_y+ty << ")";
260 
261  const bool needClipping = startPos != 0 || endPos != m_len;
262 
263  if (needClipping) {
264  p->save();
265 
266  int visualSelectionStart = f->width(text->str->s, text->str->l, m_start, startPos, false, m_start, m_start + m_len, m_toAdd);
267  int visualSelectionEnd = f->width(text->str->s, text->str->l, m_start, endPos, false, m_start, m_start + m_len, m_toAdd);
268  int visualSelectionWidth = visualSelectionEnd - visualSelectionStart;
269  if (m_reversed) {
270  visualSelectionStart = f->width(text->str->s, text->str->l, m_start, m_len, false) - visualSelectionEnd;
271  }
272 
273  QRect selectionRect(m_x + tx + visualSelectionStart, m_y + ty, visualSelectionWidth, height());
274  QRegion r(selectionRect);
275  if (p->hasClipping()) {
276  r &= p->clipRegion();
277  }
279  }
280 
281  f->drawText(p, m_x + tx, m_y + ty + m_baseline, text->str->s, text->str->l,
282  m_start, m_len, m_toAdd,
283  m_reversed ? Qt::RightToLeft : Qt::LeftToRight,
284  needClipping ? 0 : startPos, needClipping ? m_len : endPos,
285  hbg, m_y + ty, height(), deco);
286 
287  if (needClipping) {
288  p->restore();
289  }
290 }
291 
292 void InlineTextBox::paintDecoration(QPainter *pt, const Font *f, int _tx, int _ty, int deco)
293 {
294  _tx += m_x;
295  _ty += m_y;
296 
297  if (m_truncation == cFullTruncation) {
298  return;
299  }
300 
301  int width = m_width - 1;
302  if (m_truncation != cNoTruncation) {
303  width = static_cast<RenderText *>(m_object)->width(m_start, m_truncation - m_start, m_firstLine);
304  }
305 
306  RenderObject *p = object();
307 
308  QColor underline, overline, linethrough;
309  p->getTextDecorationColors(deco, underline, overline, linethrough, p->style()->htmlHacks());
310 
311  if (deco & UNDERLINE) {
312  pt->setPen(underline);
313  f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::UNDERLINE);
314  }
315  if (deco & OVERLINE) {
316  pt->setPen(overline);
317  f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::OVERLINE);
318  }
319  if (deco & LINE_THROUGH) {
320  pt->setPen(linethrough);
321  f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::LINE_THROUGH);
322  }
323  // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to
324  // support it. Lars
325 }
326 
327 void InlineTextBox::paintShadow(QPainter *pt, const Font *f, int _tx, int _ty, const ShadowData *shadow)
328 {
329  int x = m_x + _tx + shadow->x;
330  int y = m_y + _ty + shadow->y;
331  const RenderText *text = renderText();
332 
333  if (shadow->blur <= 0) {
334  QColor c = pt->pen().color();
335  pt->setPen(shadow->color);
336  f->drawText(pt, x, y + m_baseline, text->str->s, text->str->l,
337  m_start, m_len, m_toAdd,
338  m_reversed ? Qt::RightToLeft : Qt::LeftToRight);
339  pt->setPen(c);
340 
341  } else {
342  const int thickness = shadow->blur;
343  const int w = m_width + 2 * thickness;
344  const int h = m_height + 2 * thickness;
345  const QRgb color = shadow->color.rgba();
346  const int gray = qGray(color);
347  const bool inverse = (gray < 100);
348  const QRgb bgColor = (inverse) ? qRgb(255, 255, 255) : qRgb(0, 0, 0);
349  QImage img(w, h, QImage::Format_RGB32);
350  img.fill(bgColor);
351  QPainter p;
352 
353  p.begin(&img);
354  p.setPen(shadow->color);
355  p.setFont(pt->font());
356  f->drawText(&p, thickness, thickness + m_baseline, text->str->s, text->str->l,
357  m_start, m_len, m_toAdd,
358  m_reversed ? Qt::RightToLeft : Qt::LeftToRight);
359 
360  p.end();
361 
362  int md = thickness * thickness; // max-dist^2
363 
364  // blur map (division cache)
365  float *bmap = (float *)alloca(sizeof(float) * (md + 1));
366  for (int n = 0; n <= md; n++) {
367  float f;
368  f = n / (float)(md + 1);
369  f = 1.0 - f * f;
370  bmap[n] = f;
371  }
372 
373  float factor = 0.0; // maximal potential opacity-sum
374  for (int n = -thickness; n <= thickness; n++)
375  for (int m = -thickness; m <= thickness; m++) {
376  int d = n * n + m * m;
377  if (d <= md) {
378  factor += bmap[d];
379  }
380  }
381 
382  // arbitratry factor adjustment to make shadows solid.
383  factor = factor / 1.333;
384 
385  // alpha map
386  float *amap = (float *)alloca(sizeof(float) * (h * w));
387  memset(amap, 0, h * w * (sizeof(float)));
388  for (int j = thickness; j < h - thickness; j++) {
389  const QRgb *line = (QRgb *)img.scanLine(j);
390  for (int i = thickness; i < w - thickness; i++) {
391  QRgb col = line[i];
392  if (col == bgColor) {
393  continue;
394  }
395  float g = qGray(col);
396  if (inverse) {
397  g = (255 - g) / (255 - gray);
398  } else {
399  g = g / gray;
400  }
401  for (int n = -thickness; n <= thickness; n++) {
402  for (int m = -thickness; m <= thickness; m++) {
403  int d = n * n + m * m;
404  if (d > md) {
405  continue;
406  }
407  float f = bmap[d];
408  amap[(i + m) + (j + n)*w] += (g * f);
409  }
410  }
411  }
412  }
413 
414  QImage res(w, h, QImage::Format_ARGB32);
415  int r = qRed(color);
416  int g = qGreen(color);
417  int b = qBlue(color);
418 
419  // divide by factor
420  factor = 1.0 / factor;
421 
422  for (int j = 0; j < h; j++) {
423  QRgb *line = (QRgb *)res.scanLine(j);
424  for (int i = 0; i < w; i++) {
425  int a = (int)(amap[i + j * w] * factor * 255.0);
426  if (a > 255) {
427  a = 255;
428  }
429  line[i] = qRgba(r, g, b, a);
430  }
431  }
432 
433  pt->drawImage(x - thickness, y - thickness, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
434  }
435  // Paint next shadow effect
436  if (shadow->next) {
437  paintShadow(pt, f, _tx, _ty, shadow->next);
438  }
439 }
440 
441 /**
442  * Distributes pixels to justify text.
443  * @param numSpaces spaces left, will be decremented by one
444  * @param toAdd number of pixels left to be distributed, will have the
445  * amount of pixels distributed during this call subtracted.
446  * @return number of pixels to distribute
447  */
448 static inline int justifyWidth(int &numSpaces, int &toAdd)
449 {
450  int a = 0;
451  if (numSpaces) {
452  a = toAdd / numSpaces;
453  toAdd -= a;
454  numSpaces--;
455  }/*end if*/
456  return a;
457 }
458 
459 FindSelectionResult InlineTextBox::checkSelectionPoint(int _x, int _y, int _tx, int _ty, int &offset)
460 {
461 // qCDebug(KHTML_LOG) << "InlineTextBox::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
462 // << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y;
463  offset = 0;
464 
465  if (_y < _ty + m_y) {
466  return SelectionPointBefore; // above -> before
467  }
468 
469  if (_y > _ty + m_y + m_height) {
470  // below -> after
471  // Set the offset to the max
472  offset = m_len;
473  return SelectionPointAfter;
474  }
475  if (_x > _tx + m_x + m_width) {
476  // to the right
477  return SelectionPointAfterInLine;
478  }
479 
480  // The Y matches, check if we're on the left
481  if (_x < _tx + m_x) {
482  return SelectionPointBeforeInLine;
483  }
484 
485  // consider spacing for justified text
486  int toAdd = m_toAdd;
487  RenderText *text = static_cast<RenderText *>(object());
488  Q_ASSERT(text->isText());
489  bool justified = text->style()->textAlign() == JUSTIFY && toAdd > 0;
490  int numSpaces = 0;
491  if (justified) {
492  for (int i = 0; i < m_len; i++)
493  if (text->str->s[m_start + i].category() == QChar::Separator_Space) {
494  numSpaces++;
495  }
496 
497  }/*end if*/
498 
499  int delta = _x - (_tx + m_x);
500  //qCDebug(KHTML_LOG) << "InlineTextBox::checkSelectionPoint delta=" << delta;
501  int pos = 0;
502  const Font *f = text->htmlFont(m_firstLine);
503  if (m_reversed) {
504  delta -= m_width;
505  while (pos < m_len) {
506  int w = f->charWidth(text->str->s, text->str->l, m_start + pos, text->isSimpleText());
507  if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) {
508  w += justifyWidth(numSpaces, toAdd);
509  }
510  int w2 = w / 2;
511  w -= w2;
512  delta += w2;
513  if (delta >= 0) {
514  break;
515  }
516  pos++;
517  delta += w;
518  }
519  } else {
520  while (pos < m_len) {
521  int w = f->charWidth(text->str->s, text->str->l, m_start + pos, text->isSimpleText());
522  if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) {
523  w += justifyWidth(numSpaces, toAdd);
524  }
525  int w2 = w / 2;
526  w -= w2;
527  delta -= w2;
528  if (delta <= 0) {
529  break;
530  }
531  pos++;
532  delta -= w;
533  }
534  }
535 // qCDebug(KHTML_LOG) << " Text --> inside at position " << pos;
536  offset = pos;
537  return SelectionPointInside;
538 }
539 
540 long InlineTextBox::caretMinOffset() const
541 {
542  return m_start;
543 }
544 
545 long InlineTextBox::caretMaxOffset() const
546 {
547  return m_start + m_len;
548 }
549 
550 unsigned long InlineTextBox::caretMaxRenderedOffset() const
551 {
552  return m_start + m_len;
553 }
554 
555 int InlineTextBox::offsetForPoint(int _x, int &ax) const
556 {
557  // Do binary search for finding out offset, saves some time for long
558  // runs.
559  int start = 0;
560  int end = m_len;
561  ax = m_x;
562  int offset = (start + end) / 2;
563  while (end - start > 0) {
564  // always snap to the right column. This makes up for "jumpy" vertical
565  // navigation.
566  if (end - start == 1) {
567  start = end;
568  }
569 
570  offset = (start + end) / 2;
571  ax = m_x + widthFromStart(offset);
572  if (ax > _x) {
573  end = offset;
574  } else if (ax < _x) {
575  start = offset;
576  } else {
577  break;
578  }
579  }
580  return m_start + offset;
581 }
582 
583 int InlineTextBox::widthFromStart(int pos) const
584 {
585  // gasp! sometimes pos is i < 0 which crashes Font::width
586  // qCDebug(KHTML_LOG) << this << pos;
587  pos = qMax(pos, 0);
588 
589  const RenderText *t = renderText();
590  Q_ASSERT(t->isText());
591  const Font *f = t->htmlFont(m_firstLine);
592  const QFontMetrics &fm = t->fontMetrics(m_firstLine);
593 
594  int numSpaces = 0;
595  // consider spacing for justified text
596  bool justified = t->style()->textAlign() == JUSTIFY;
597  //qCDebug(KHTML_LOG) << "InlineTextBox::width(int)";
598  if (justified && m_toAdd > 0) do {
599  //qCDebug(KHTML_LOG) << "justify";
600 
601 // const QString cstr = QString::fromRawData(t->str->s + m_start, m_len);
602  for (int i = 0; i < m_len; i++)
603  if (t->str->s[m_start + i].category() == QChar::Separator_Space) {
604  numSpaces++;
605  }
606  if (numSpaces == 0) {
607  break;
608  }
609 
610  int toAdd = m_toAdd;
611  int w = 0; // accumulated width
612  int start = 0; // start of non-space sequence
613  int current = 0; // current position
614  while (current < pos) {
615  // add spacing
616  while (current < pos && t->str->s[m_start + current].category() == QChar::Separator_Space) {
617  w += f->getWordSpacing();
618  w += f->getLetterSpacing();
619  w += justifyWidth(numSpaces, toAdd);
620  w += fm.width(' '); // ### valid assumption? (LS)
621  current++; start++;
622  }/*wend*/
623  if (current >= pos) {
624  break;
625  }
626 
627  // seek next space
628  while (current < pos && t->str->s[m_start + current].category() != QChar::Separator_Space) {
629  current++;
630  }
631 
632  // check run without spaces
633  if (current > start) {
634  w += f->width(t->str->s + m_start, m_len, start, current - start, false);
635  start = current;
636  }
637  }
638 
639  return w;
640 
641  } while (false); /*end if*/
642 
643  //qCDebug(KHTML_LOG) << "default";
644  // else use existing width function
645  // qCDebug(KHTML_LOG) << "result width:" << f->width(t->str->s + m_start, m_len, 0, pos, false);
646  return f->width(t->str->s + m_start, m_len, 0, pos, false);
647 
648 }
649 
650 void InlineTextBox::deleteLine(RenderArena *arena)
651 {
652  static_cast<RenderText *>(m_object)->removeTextBox(this);
653  detach(arena, true /*noRemove*/);
654 }
655 
656 void InlineTextBox::extractLine()
657 {
658  if (m_extracted) {
659  return;
660  }
661  static_cast<RenderText *>(m_object)->extractTextBox(this);
662 }
663 
664 void InlineTextBox::attachLine()
665 {
666  if (!m_extracted) {
667  return;
668  }
669  static_cast<RenderText *>(m_object)->attachTextBox(this);
670 }
671 
672 int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool &foundBox)
673 {
674  if (foundBox) {
675  m_truncation = cFullTruncation;
676  return -1;
677  }
678 
679  int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth;
680 
681  // For LTR, if the left edge of the ellipsis is to the left of our text run, then we are the run that will get truncated.
682  if (ltr) {
683  if (ellipsisX <= m_x) {
684  // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
685  m_truncation = cFullTruncation;
686  foundBox = true;
687  return -1;
688  }
689 
690  if (ellipsisX < m_x + m_width) {
691  if (m_reversed) {
692  return -1; // FIXME: Support LTR truncation when the last run is RTL someday.
693  }
694 
695  foundBox = true;
696 
697  int ax;
698  int offset = offsetForPoint(ellipsisX, ax) - 1;
699  if (offset <= m_start) {
700  // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start
701  // and the ellipsis edge.
702  m_truncation = cFullTruncation;
703  return qMin(ellipsisX, (int)m_x);
704  }
705 
706  // Set the truncation index on the text run. The ellipsis needs to be placed just after the last visible character.
707  m_truncation = offset;
708  return widthFromStart(offset - m_start);
709  }
710  } else {
711  // FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR)
712  }
713  return -1;
714 }
715 
716 // -----------------------------------------------------------------------------
717 
718 RenderText::RenderText(DOM::NodeImpl *node, DOMStringImpl *_str)
719  : RenderObject(node)
720 {
721  // init RenderObject attributes
722  setRenderText(); // our object inherits from RenderText
723 
724  m_minWidth = -1;
725  m_maxWidth = -1;
726  str = _str;
727  if (str) {
728  str->ref();
729  }
730  KHTMLAssert(!str || !str->l || str->s);
731 
732  m_selectionState = SelectionNone;
733  m_hasReturn = true;
734  m_isSimpleText = false;
735  m_firstTextBox = m_lastTextBox = nullptr;
736 
737 #ifdef DEBUG_LAYOUT
738  const QString cstr = QString::fromRawData(str->s, str->l);
739  qCDebug(KHTML_LOG) << "RenderText ctr( " << cstr.length() << " ) '" << cstr << "'";
740 #endif
741 }
742 
743 void RenderText::setStyle(RenderStyle *_style)
744 {
745  if (style() != _style) {
746  bool changedText = ((!style() && (_style->textTransform() != TTNONE ||
747  !_style->preserveLF() || !_style->preserveWS())) ||
748  (style() && (style()->textTransform() != _style->textTransform() ||
749  style()->whiteSpace() != _style->whiteSpace())));
750 
751  RenderObject::setStyle(_style);
752  m_lineHeight = RenderObject::lineHeight(false);
753 
754  if (!isBR() && changedText) {
755  DOM::DOMStringImpl *textToTransform = originalString();
756  if (textToTransform) {
757  setText(textToTransform, true);
758  }
759  }
760  }
761 }
762 
763 RenderText::~RenderText()
764 {
765  if (str) {
766  str->deref();
767  }
768  assert(!m_firstTextBox);
769  assert(!m_lastTextBox);
770 }
771 
772 void RenderText::detach()
773 {
774  if (!documentBeingDestroyed()) {
775  if (firstTextBox()) {
776  if (isBR()) {
777  RootInlineBox *next = firstTextBox()->root()->nextRootBox();
778  if (next) {
779  next->markDirty();
780  }
781  }
782  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
783  box->remove();
784  }
785  } else if (parent()) {
786  parent()->dirtyLinesFromChangedChild(this);
787  }
788  }
789  deleteInlineBoxes();
790  RenderObject::detach();
791 }
792 
793 void RenderText::extractTextBox(InlineTextBox *box)
794 {
795  m_lastTextBox = box->prevTextBox();
796  if (box == m_firstTextBox) {
797  m_firstTextBox = nullptr;
798  }
799  if (box->prevTextBox()) {
800  box->prevTextBox()->setNextLineBox(nullptr);
801  }
802  box->setPreviousLineBox(nullptr);
803  for (InlineRunBox *curr = box; curr; curr = curr->nextLineBox()) {
804  curr->setExtracted();
805  }
806 }
807 
808 void RenderText::attachTextBox(InlineTextBox *box)
809 {
810  if (m_lastTextBox) {
811  m_lastTextBox->setNextLineBox(box);
812  box->setPreviousLineBox(m_lastTextBox);
813  } else {
814  m_firstTextBox = box;
815  }
816  InlineTextBox *last = box;
817  for (InlineTextBox *curr = box; curr; curr = curr->nextTextBox()) {
818  curr->setExtracted(false);
819  last = curr;
820  }
821  m_lastTextBox = last;
822 }
823 
824 void RenderText::removeTextBox(InlineTextBox *box)
825 {
826  if (box == m_firstTextBox) {
827  m_firstTextBox = box->nextTextBox();
828  }
829  if (box == m_lastTextBox) {
830  m_lastTextBox = box->prevTextBox();
831  }
832  if (box->nextTextBox()) {
833  box->nextTextBox()->setPreviousLineBox(box->prevTextBox());
834  }
835  if (box->prevTextBox()) {
836  box->prevTextBox()->setNextLineBox(box->nextTextBox());
837  }
838 }
839 
840 void RenderText::removeInlineBox(InlineBox *_box)
841 {
842  KHTMLAssert(_box->isInlineTextBox());
843  removeTextBox(static_cast<InlineTextBox *>(_box));
844 }
845 
846 void RenderText::deleteInlineBoxes(RenderArena * /*arena*/)
847 {
848  if (firstTextBox()) {
849  RenderArena *arena = renderArena();
850  InlineTextBox *next;
851  for (InlineTextBox *curr = firstTextBox(); curr; curr = next) {
852  next = curr->nextTextBox();
853  curr->detach(arena, true /*noRemove*/);
854  }
855  m_firstTextBox = m_lastTextBox = nullptr;
856  }
857 }
858 
859 void RenderText::dirtyInlineBoxes(bool fullLayout, bool)
860 {
861  if (fullLayout) {
862  deleteInlineBoxes();
863  } else {
864  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
865  box->dirtyInlineBoxes();
866  }
867  }
868 }
869 
870 bool RenderText::isTextFragment() const
871 {
872  return false;
873 }
874 
875 DOM::DOMStringImpl *RenderText::originalString() const
876 {
877  return element() ? element()->string() : nullptr;
878 }
879 
880 const InlineTextBox *RenderText::findInlineTextBox(int offset, int &pos, bool checkFirstLetter) const
881 {
882  Q_UNUSED(checkFirstLetter);
883  // The text boxes point to parts of the rendertext's str string
884  // (they don't include '\n')
885  // Find the text box that includes the character at @p offset
886  // and return pos, which is the position of the char in the run.
887 
888  if (!m_firstTextBox) {
889  return nullptr;
890  }
891 
892  InlineTextBox *s = m_firstTextBox;
893  int off = s->m_len;
894  while (offset > off && s->nextTextBox()) {
895  s = s->nextTextBox();
896  off = s->m_start + s->m_len;
897  }
898  // we are now in the correct text run
899  if (offset >= s->m_start && offset < s->m_start + s->m_len) {
900  pos = offset - s->m_start;
901  } else {
902  pos = (offset > off ? s->m_len : s->m_len - (off - offset));
903  }
904  return s;
905 }
906 
907 bool RenderText::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction /*hitTestAction*/, bool /*inBox*/)
908 {
909  assert(parent());
910 
911  bool inside = false;
912  if (style()->visibility() != HIDDEN) {
913  for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) {
914  if ((_y >= _ty + s->m_y) && (_y < _ty + s->m_y + s->m_height) &&
915  (_x >= _tx + s->m_x) && (_x < _tx + s->m_x + s->m_width)) {
916  inside = true;
917  break;
918  }
919  }
920  }
921 
922  // #### ported over from Safari. Can this happen at all? (lars)
923 
924  if (inside && element()) {
925  if (info.innerNode() && info.innerNode()->renderer() &&
926  !info.innerNode()->renderer()->isInline()) {
927  // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node.
928  info.setInnerNode(element());
929 
930  // Clear everything else.
931  info.setInnerNonSharedNode(nullptr);
932  info.setURLElement(nullptr);
933  }
934 
935  if (!info.innerNode()) {
936  info.setInnerNode(element());
937  }
938 
939  if (!info.innerNonSharedNode()) {
940  info.setInnerNonSharedNode(element());
941  }
942  }
943 
944  return inside;
945 }
946 
947 FindSelectionResult RenderText::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl *&node, int &offset, SelPointState &)
948 {
949 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
950 // << " _tx=" << _tx << " _ty=" << _ty;
951 //qCDebug(KHTML_LOG) << renderName() << "::checkSelectionPoint x=" << xPos() << " y=" << yPos() << " w=" << width() << " h=" << height();
952 
953  NodeImpl *lastNode = nullptr;
954  int lastOffset = 0;
955  FindSelectionResult lastResult = SelectionPointAfter;
956 
957  for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) {
958  FindSelectionResult result;
959 // #### ?
960 // result = s->checkSelectionPoint(_x, _y, _tx, _ty, offset);
961  if (_y < _ty + s->m_y) {
962  result = SelectionPointBefore;
963  } else if (_y >= _ty + s->m_y + s->height()) {
964  result = SelectionPointAfterInLine;
965  } else if (_x < _tx + s->m_x) {
966  result = SelectionPointBeforeInLine;
967  } else if (_x >= _tx + s->m_x + s->width()) {
968  result = SelectionPointAfterInLine;
969  } else {
970  int dummy;
971  result = SelectionPointInside;
972  // offsetForPoint shifts to the right: correct it
973  offset = s->offsetForPoint(_x - _tx, dummy) - 1;
974  }
975 
976 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset;
977  if (result == SelectionPointInside) { // x,y is inside the textrun
978 // offset += s->m_start; // add the offset from the previous lines
979 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint inside -> " << offset;
980  node = element();
981  return SelectionPointInside;
982  } else if (result == SelectionPointBefore) {
983  if (!lastNode) {
984  // x,y is before the textrun -> stop here
985  offset = 0;
986 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before";
987  node = element();
988  return SelectionPointBefore;
989  }
990  } else if (result == SelectionPointBeforeInLine) {
991  offset = s->m_start;
992  node = element();
993  return SelectionPointInside;
994  } else if (result == SelectionPointAfterInLine) {
995  lastOffset = s->m_start + s->m_len;
996  lastNode = element();
997  lastResult = result;
998  // no return here
999  }
1000 
1001  }
1002 
1003  if (lastNode) {
1004  offset = lastOffset;
1005  node = lastNode;
1006 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint: lastNode " << lastNode << " lastOffset " << lastOffset;
1007  return lastResult;
1008  }
1009 
1010  // set offset to max
1011  offset = str->l;
1012  //qDebug("setting node to %p", element());
1013  node = element();
1014 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint: node " << node << " offset " << offset;
1015  return SelectionPointAfter;
1016 }
1017 
1018 unsigned RenderText::convertToDOMPosition(unsigned position) const
1019 {
1020  if (isBR()) {
1021  return 0;
1022  }
1023  /*const */DOMStringImpl *domString = originalString();
1024  /*const */DOMStringImpl *renderedString = string();
1025  if (domString == renderedString) {
1026  // qCDebug(KHTML_LOG) << "[rendered == dom]" << position;
1027  return position;
1028  }
1029  /* // qCDebug(KHTML_LOG) << "[convert]" << position << endl
1030  << DOMString(domString) << endl
1031  << DOMString(renderedString);*/
1032 
1033  if (!domString || !renderedString) {
1034  return position;
1035  }
1036 
1037  unsigned domLength = domString->length();
1038  unsigned i = 0, j = 0;
1039  for (; i < domLength && j < position;) {
1040  bool isRenderedSpace = renderedString->unicode()[j].isSpace();
1041  bool isDOMSpace = domString->unicode()[i].isSpace();
1042  if (isRenderedSpace && isDOMSpace) {
1043  ++i;
1044  ++j;
1045  continue;
1046  }
1047  if (isRenderedSpace) {
1048  ++j;
1049  continue;
1050  }
1051  if (isDOMSpace) {
1052  ++i;
1053  continue;
1054  }
1055  ++i;
1056  ++j;
1057  }
1058  // qCDebug(KHTML_LOG) << "[result]" << i;
1059  return i;
1060 }
1061 
1062 unsigned RenderText::convertToRenderedPosition(unsigned position) const
1063 {
1064  if (isBR()) {
1065  return 0;
1066  }
1067  /*const */DOMStringImpl *domString = originalString();
1068  /*const */DOMStringImpl *renderedString = string();
1069  if (domString == renderedString) {
1070  // qCDebug(KHTML_LOG) << "[rendered == dom]" << position;
1071  return position;
1072  }
1073  /* // qCDebug(KHTML_LOG) << "[convert]" << position << endl
1074  << DOMString(domString) << endl
1075  << DOMString(renderedString);*/
1076 
1077  if (!domString || !renderedString) {
1078  return position;
1079  }
1080 
1081  unsigned renderedLength = renderedString->length();
1082  unsigned i = 0, j = 0;
1083  for (; i < position && j < renderedLength;) {
1084  bool isRenderedSpace = renderedString->unicode()[j].isSpace();
1085  bool isDOMSpace = domString->unicode()[i].isSpace();
1086  if (isRenderedSpace && isDOMSpace) {
1087  ++i;
1088  ++j;
1089  continue;
1090  }
1091  if (isRenderedSpace) {
1092  ++j;
1093  continue;
1094  }
1095  if (isDOMSpace) {
1096  ++i;
1097  continue;
1098  }
1099  ++i;
1100  ++j;
1101  }
1102  // qCDebug(KHTML_LOG) << "[result]" << j;
1103  return j;
1104 }
1105 
1106 RenderPosition RenderText::positionForCoordinates(int _x, int _y)
1107 {
1108  // qCDebug(KHTML_LOG) << this << _x << _y;
1109  if (!firstTextBox() || stringLength() == 0) {
1110  return Position(element(), 0);
1111  }
1112 
1113  int absx, absy;
1114  containingBlock()->absolutePosition(absx, absy);
1115  // qCDebug(KHTML_LOG) << "absolute(" << absx << absy << ")";
1116 
1117  if (_y < absy + firstTextBox()->root()->bottomOverflow() && _x < absx + firstTextBox()->m_x) {
1118  // at the y coordinate of the first line or above
1119  // and the x coordinate is to the left than the first text box left edge
1120  return RenderPosition(element(), firstTextBox()->m_start);
1121  }
1122 
1123  if (_y >= absy + lastTextBox()->root()->topOverflow() && _x >= absx + lastTextBox()->m_x + lastTextBox()->m_width) {
1124  // at the y coordinate of the last line or below
1125  // and the x coordinate is to the right than the last text box right edge
1126  return RenderPosition(element(), lastTextBox()->m_start + lastTextBox()->m_len);
1127  }
1128 
1129  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1130  // qCDebug(KHTML_LOG) << "[check box]" << box;
1131  if (_y >= absy + box->root()->topOverflow() && _y < absy + box->root()->bottomOverflow()) {
1132  if (_x < absx + box->m_x + box->m_width) {
1133  // and the x coordinate is to the left of the right edge of this box
1134  // check to see if position goes in this box
1135  int offset;
1136  box->checkSelectionPoint(_x, absy + box->yPos(), absx, absy, offset);
1137  // qCDebug(KHTML_LOG) << "offset" << offset;
1138  if (offset != -1) {
1139  // qCDebug(KHTML_LOG) << "return" << Position(element(), convertToDOMPosition(offset + box->m_start));
1140  return RenderPosition(element(), offset + box->m_start);
1141  }
1142  } else if (!box->prevOnLine() && _x < absx + box->m_x)
1143  // box is first on line
1144  // and the x coordinate is to the left than the first text box left edge
1145  {
1146  return RenderPosition(element(), box->m_start);
1147  } else if (!box->nextOnLine() && _x >= absx + box->m_x + box->m_width)
1148  // box is last on line
1149  // and the x coordinate is to the right than the last text box right edge
1150  {
1151  return RenderPosition(element(), box->m_start + box->m_len);
1152  }
1153  }
1154  }
1155  return RenderPosition(element(), 0);
1156 }
1157 
1158 void RenderText::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) const
1159 {
1160  // qCDebug(KHTML_LOG) << offset << flags;
1161  if (!m_firstTextBox) {
1162  _x = _y = height = -1;
1163  width = 1;
1164  return;
1165  }
1166 
1167  int pos;
1168  const InlineTextBox *s = findInlineTextBox(offset, pos, true);
1169  const RenderText *t = s->renderText();
1170 // qCDebug(KHTML_LOG) << "offset="<<offset << " pos="<<pos;
1171 
1172  const QFontMetrics &fm = t->metrics(s->m_firstLine);
1173  height = fm.height(); // s->m_height;
1174 
1175  _x = s->m_x + s->widthFromStart(pos);
1176  _y = s->m_y + s->baseline() - fm.ascent();
1177  // qCDebug(KHTML_LOG) << "(" << _x << _y << ")";
1178  width = 1;
1179  if (flags & CFOverride) {
1180  width = offset < caretMaxOffset() ? fm.width(str->s[offset]) : 1;
1181  // qCDebug(KHTML_LOG) << "CFOverride" << width;
1182  }/*end if*/
1183 #if 0
1184  // qCDebug(KHTML_LOG) << "_x="<<_x << " s->m_x="<<s->m_x
1185  << " s->m_start" << s->m_start
1186  << " s->m_len" << s->m_len << " _y=" << _y;
1187 #endif
1188 
1189  int absx, absy;
1190 
1191  if (absolutePosition(absx, absy)) {
1192  //qCDebug(KHTML_LOG) << "absx=" << absx << " absy=" << absy;
1193  _x += absx;
1194  _y += absy;
1195  } else {
1196  // we don't know our absolute position, and there is no point returning
1197  // just a relative one
1198  _x = _y = -1;
1199  }
1200 }
1201 
1202 long RenderText::caretMinOffset() const
1203 {
1204  if (!m_firstTextBox) {
1205  return 0;
1206  }
1207  // FIXME: it is *not* guaranteed that the first run contains the lowest offset
1208  // Either make this a linear search (slow),
1209  // or maintain an index (needs much mem),
1210  // or calculate and store it in bidi.cpp (needs calculation even if not needed)
1211  // (LS)
1212  return m_firstTextBox->m_start;
1213 }
1214 
1215 long RenderText::caretMaxOffset() const
1216 {
1217  InlineTextBox *box = m_lastTextBox;
1218  if (!box) {
1219  return str->l;
1220  }
1221  int maxOffset = box->m_start + box->m_len;
1222  // ### slow
1223  for (box = box->prevTextBox(); box; box = box->prevTextBox()) {
1224  maxOffset = qMax(maxOffset, box->m_start + box->m_len);
1225  }
1226  return maxOffset;
1227 }
1228 
1229 unsigned long RenderText::caretMaxRenderedOffset() const
1230 {
1231  int l = 0;
1232  // ### no guarantee that the order is ascending
1233  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1234  l += box->m_len;
1235  }
1236  return l;
1237 }
1238 
1239 InlineBox *RenderText::inlineBox(long offset)
1240 {
1241  // ### make educated guess about most likely position
1242  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1243  if (offset >= box->m_start && offset <= box->m_start + box->m_len) {
1244  return box;
1245  } else if (offset < box->m_start) {
1246  // The offset we're looking for is before this node
1247  // this means the offset must be in content that is
1248  // not rendered.
1249  return box->prevTextBox() ? box->prevTextBox() : firstTextBox();
1250  }
1251  }
1252 
1253  return nullptr;
1254 }
1255 
1256 bool RenderText::absolutePosition(int &xPos, int &yPos, bool) const
1257 {
1258  return RenderObject::absolutePosition(xPos, yPos, false);
1259 }
1260 
1261 bool RenderText::posOfChar(int chr, int &x, int &y) const
1262 {
1263  if (!parent()) {
1264  return false;
1265  }
1266  parent()->absolutePosition(x, y, false);
1267 
1268  int pos;
1269  const InlineTextBox *s = findInlineTextBox(chr, pos);
1270 
1271  if (s) {
1272  // s is the line containing the character
1273  x += s->m_x; // this is the x of the beginning of the line, but it's good enough for now
1274  y += s->m_y;
1275  return true;
1276  }
1277 
1278  return false;
1279 }
1280 
1281 static bool isSimpleChar(const unsigned short c)
1282 {
1283  // Exclude ranges with many Mn/Me/Mc and the various combining diacriticals ranges.
1284  // Unicode version used is 4.1.0
1285 
1286  // General Combining Diacritical Marks
1287  if (c < 0x300) {
1288  return true;
1289  }
1290  if (c <= 0x36F) {
1291  return false;
1292  }
1293 
1294  // Cyrillic's
1295  if (c < 0x483) {
1296  return true;
1297  }
1298  if (c <= 0x489) {
1299  return false;
1300  }
1301 
1302  // Hebrew's
1303  if (c < 0x0591) {
1304  return true;
1305  }
1306  if (c <= 0x05C7 && !(c == 0x05BE || c == 0x05C0 || c == 0x05C3 || c == 0x05C6)) {
1307  return false;
1308  }
1309 
1310  // Unicode range 6 to 11 (Arabic to Korean Hangul)
1311  if (c < 0x0600) {
1312  return true;
1313  }
1314  if (c <= 0x11F9) {
1315  return false;
1316  }
1317 
1318  // Unicode range 17 to 1A (Tagalog to Buginese)
1319  // (also excl. Ethiopic Combining Gemination Mark)
1320  if (c < 0x1700 && c != 0x135F) {
1321  return true;
1322  }
1323  if (c <= 0x1A1F) {
1324  return false;
1325  }
1326 
1327  // Combining Diacritical Marks Supplement
1328  if (c < 0x1DC0) {
1329  return true;
1330  }
1331  if (c <= 0x1DFF) {
1332  return false;
1333  }
1334 
1335  // Diacritical Marks for Symbols
1336  if (c < 0x20D0) {
1337  return true;
1338  }
1339  if (c <= 0x20EB) {
1340  return false;
1341  }
1342 
1343  // Combining Half Marks
1344  if (c < 0xFE20) {
1345  return true;
1346  }
1347  if (c <= 0xFE2F) {
1348  return false;
1349  }
1350 
1351  return true;
1352 }
1353 
1354 void RenderText::calcMinMaxWidth()
1355 {
1356  KHTMLAssert(!minMaxKnown());
1357 
1358  // ### calc Min and Max width...
1359  m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
1360  m_maxWidth = 0;
1361 
1362  if (isBR()) {
1363  return;
1364  }
1365 
1366  int currMinWidth = 0;
1367  int currMaxWidth = 0;
1368  m_isSimpleText = true;
1369  m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
1370 
1371  // ### not 100% correct for first-line
1372  const Font *f = htmlFont(false);
1373  int wordSpacing = style()->wordSpacing();
1374  int len = str->l;
1375  bool isSpace = false;
1376  bool firstWord = true;
1377  bool firstLine = true;
1378  for (int i = 0; i < len; i++) {
1379  unsigned short c = str->s[i].unicode();
1380  bool isNewline = false;
1381 
1382  // If line-breaks survive to here they are preserved
1383  if (c == '\n') {
1384  if (style()->preserveLF()) {
1385  m_hasBreak = true;
1386  isNewline = true;
1387  isSpace = false;
1388  } else {
1389  isSpace = true;
1390  }
1391  } else {
1392  isSpace = c == ' ';
1393  }
1394 
1395  if ((isSpace || isNewline) && i == 0) {
1396  m_hasBeginWS = true;
1397  }
1398  if ((isSpace || isNewline) && i == len - 1) {
1399  m_hasEndWS = true;
1400  }
1401 
1402  if (i && c == SOFT_HYPHEN) {
1403  continue;
1404  }
1405 
1406  int wordlen = 0;
1407  while (i + wordlen < len && (i + wordlen == 0 || str->s[i + wordlen].unicode() != SOFT_HYPHEN) &&
1408  !(isBreakable(str->s, i + wordlen, str->l))) {
1409  // check if we may use the simpler algorithm for estimating text width
1410  m_isSimpleText = (m_isSimpleText && isSimpleChar(str->s[i + wordlen].unicode()));
1411  wordlen++;
1412  }
1413 
1414  if (wordlen) {
1415  int w = f->width(str->s, str->l, i, wordlen, m_isSimpleText);
1416  currMinWidth += w;
1417  currMaxWidth += w;
1418 
1419  // Add in wordspacing to our maxwidth, but not if this is the last word.
1420  if (wordSpacing && !containsOnlyWhitespace(i + wordlen, len - (i + wordlen))) {
1421  currMaxWidth += wordSpacing;
1422  }
1423 
1424  if (firstWord) {
1425  firstWord = false;
1426  m_beginMinWidth = w;
1427  }
1428  m_endMinWidth = w;
1429 
1430  if (currMinWidth > m_minWidth) {
1431  m_minWidth = currMinWidth;
1432  }
1433  currMinWidth = 0;
1434 
1435  i += wordlen - 1;
1436  } else {
1437  // Nowrap can never be broken, so don't bother setting the
1438  // breakable character boolean. Pre can only be broken if we encounter a newline.
1439  if (style()->autoWrap() || isNewline) {
1440  m_hasBreakableChar = true;
1441  }
1442 
1443  if (currMinWidth > m_minWidth) {
1444  m_minWidth = currMinWidth;
1445  }
1446  currMinWidth = 0;
1447 
1448  if (isNewline) { // Only set if isPre was true and we saw a newline.
1449  if (firstLine) {
1450  firstLine = false;
1451  if (!style()->autoWrap()) {
1452  m_beginMinWidth = currMaxWidth;
1453  }
1454  }
1455 
1456  if (currMaxWidth > m_maxWidth) {
1457  m_maxWidth = currMaxWidth;
1458  }
1459  currMaxWidth = 0;
1460  } else {
1461  currMaxWidth += f->charWidth(str->s, str->l, i + wordlen, m_isSimpleText);
1462  }
1463  }
1464  }
1465 
1466  if (currMinWidth > m_minWidth) {
1467  m_minWidth = currMinWidth;
1468  }
1469  if (currMaxWidth > m_maxWidth) {
1470  m_maxWidth = currMaxWidth;
1471  }
1472 
1473  if (!style()->autoWrap()) {
1474  m_minWidth = m_maxWidth;
1475  if (style()->preserveLF()) {
1476  if (firstLine) {
1477  m_beginMinWidth = m_maxWidth;
1478  }
1479  m_endMinWidth = currMaxWidth;
1480  }
1481  }
1482 
1483  setMinMaxKnown();
1484  //qCDebug(KHTML_LOG) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth;
1485 
1486 }
1487 
1488 int RenderText::minXPos() const
1489 {
1490  if (!m_firstTextBox) {
1491  return 0;
1492  }
1493  int retval = 6666666;
1494  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1495  retval = qMin(retval, static_cast<int>(box->m_x));
1496  }
1497  return retval;
1498 }
1499 
1500 int RenderText::inlineXPos() const
1501 {
1502  return minXPos();
1503 }
1504 
1505 int RenderText::inlineYPos() const
1506 {
1507  return m_firstTextBox ? m_firstTextBox->yPos() : 0;
1508 }
1509 
1510 const QFont &RenderText::font()
1511 {
1512  return style()->font();
1513 }
1514 
1515 void RenderText::setText(DOMStringImpl *text, bool force)
1516 {
1517  if (!force && str == text) {
1518  return;
1519  }
1520 
1521  setTextInternal(text);
1522 }
1523 
1524 void RenderText::setTextInternal(DOMStringImpl *text)
1525 {
1526  DOMStringImpl *oldstr = str;
1527  if (text && style()) {
1528  str = text->collapseWhiteSpace(style()->preserveLF(), style()->preserveWS());
1529  } else {
1530  str = text;
1531  }
1532 
1533  if (str) {
1534  str->ref();
1535  }
1536  if (oldstr) {
1537  oldstr->deref();
1538  }
1539 
1540  if (str && style()) {
1541  oldstr = str;
1542  switch (style()->textTransform()) {
1543  case CAPITALIZE: {
1544  RenderObject *o;
1545  bool runOnString = false;
1546  // find previous non-empty text renderer if one exists
1547  for (o = previousRenderer(); o; o = o->previousRenderer()) {
1548  if (!o->isInlineFlow()) {
1549  if (!o->isText()) {
1550  break;
1551  }
1552  DOMStringImpl *prevStr = static_cast<RenderText *>(o)->string();
1553  // !prevStr can happen with css like "content:open-quote;"
1554  if (!prevStr) {
1555  break;
1556  }
1557  if (prevStr->length() == 0) {
1558  continue;
1559  }
1560  QChar c = (*prevStr)[prevStr->length() - 1];
1561  if (!c.isSpace()) {
1562  runOnString = true;
1563  }
1564  break;
1565  }
1566  }
1567  str = str->capitalize(runOnString);
1568  break;
1569  }
1570  case UPPERCASE:
1571  str = str->upper();
1572  break;
1573  case LOWERCASE:
1574  str = str->lower();
1575  break;
1576  case TTNONE:
1577  default:
1578  break;
1579  }
1580  str->ref();
1581  oldstr->deref();
1582  }
1583 
1584  // ### what should happen if we change the text of a
1585  // RenderBR object ?
1586  KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n'));
1587  KHTMLAssert(!str->l || str->s);
1588 
1589  if (parent()) {
1590  setNeedsLayoutAndMinMaxRecalc();
1591  }
1592 #ifdef BIDI_DEBUG
1593  QString cstr = QString::fromRawData(str->s, str->l);
1594  qCDebug(KHTML_LOG) << "RenderText::setText( " << cstr.length() << " ) '" << cstr << "'";
1595 #endif
1596 }
1597 
1598 int RenderText::height() const
1599 {
1600  int retval = 0;
1601  if (firstTextBox())
1602 #ifdef I_LOVE_UPDATING_TESTREGRESSIONS_BASELINES
1603  retval = lastTextBox()->m_y + lastTextBox()->height() - firstTextBox()->m_y;
1604 #else
1605  retval = lastTextBox()->m_y + m_lineHeight - firstTextBox()->m_y;
1606  else {
1607  retval = metrics(false).height();
1608  }
1609 #endif
1610  return retval;
1611 }
1612 
1613 short RenderText::lineHeight(bool firstLine) const
1614 {
1615  if (firstLine) {
1616  return RenderObject::lineHeight(firstLine);
1617  }
1618 
1619  return m_lineHeight;
1620 }
1621 
1622 short RenderText::baselinePosition(bool firstLine) const
1623 {
1624  const QFontMetrics &fm = metrics(firstLine);
1625  return fm.ascent() +
1626  (lineHeight(firstLine) - fm.height()) / 2;
1627 }
1628 
1629 InlineBox *RenderText::createInlineBox(bool, bool isRootLineBox)
1630 {
1631  KHTMLAssert(!isRootLineBox);
1632  Q_UNUSED(isRootLineBox);
1633  InlineTextBox *textBox = new(renderArena()) InlineTextBox(this);
1634  if (!m_firstTextBox) {
1635  m_firstTextBox = m_lastTextBox = textBox;
1636  } else {
1637  m_lastTextBox->setNextLineBox(textBox);
1638  textBox->setPreviousLineBox(m_lastTextBox);
1639  m_lastTextBox = textBox;
1640  }
1641  return textBox;
1642 }
1643 
1644 void RenderText::position(InlineBox *box, int from, int len, bool reverse)
1645 {
1646 //qCDebug(KHTML_LOG) << "position: from="<<from<<" len="<<len;
1647 
1648  reverse = reverse && !style()->visuallyOrdered();
1649 
1650  KHTMLAssert(box->isInlineTextBox());
1651  InlineTextBox *s = static_cast<InlineTextBox *>(box);
1652  s->m_start = from;
1653  s->m_len = len;
1654  s->m_reversed = reverse;
1655 }
1656 
1657 unsigned int RenderText::width(unsigned int from, unsigned int len, bool firstLine) const
1658 {
1659  if (!str->s || from > str->l) {
1660  return 0;
1661  }
1662  if (from + len > str->l) {
1663  len = str->l - from;
1664  }
1665 
1666  const Font *f = htmlFont(firstLine);
1667  return width(from, len, f);
1668 }
1669 
1670 unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f) const
1671 {
1672  if (!str->s || from > str->l) {
1673  return 0;
1674  }
1675  if (from + len > str->l) {
1676  len = str->l - from;
1677  }
1678 
1679  if (f == &style()->htmlFont() && from == 0 && len == str->l) {
1680  return m_maxWidth;
1681  }
1682 
1683  int w = f->width(str->s, str->l, from, len, m_isSimpleText);
1684 
1685  //qCDebug(KHTML_LOG) << "RenderText::width(" << from << ", " << len << ") = " << w;
1686  return w;
1687 }
1688 
1689 short RenderText::width() const
1690 {
1691  int w;
1692  int minx = 100000000;
1693  int maxx = 0;
1694  // slooow
1695  for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) {
1696  if (s->m_x < minx) {
1697  minx = s->m_x;
1698  }
1699  if (s->m_x + s->m_width > maxx) {
1700  maxx = s->m_x + s->m_width;
1701  }
1702  }
1703 
1704  w = qMax(0, maxx - minx);
1705 
1706  return w;
1707 }
1708 
1709 void RenderText::repaint(Priority p)
1710 {
1711  RenderObject *cb = containingBlock();
1712  if (cb) {
1713  cb->repaint(p);
1714  }
1715 }
1716 
1717 QList< QRectF > RenderText::getClientRects()
1718 {
1720 
1721  int x = 0;
1722  int y = 0;
1723  absolutePosition(x, y);
1724 
1725  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1726  QRectF textBoxRect(box->xPos() + x, box->yPos() + y,
1727  box->width(), box->height());
1728 
1729  list.append(clientRectToViewport(textBoxRect));
1730  }
1731  return list;
1732 }
1733 
1734 bool RenderText::isFixedWidthFont() const
1735 {
1736  return QFontInfo(style()->font()).fixedPitch();
1737 }
1738 
1739 short RenderText::verticalPositionHint(bool firstLine) const
1740 {
1741  return parent()->verticalPositionHint(firstLine);
1742 }
1743 
1744 const QFontMetrics &RenderText::metrics(bool firstLine) const
1745 {
1746  if (firstLine && hasFirstLine()) {
1747  RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE);
1748  if (pseudoStyle) {
1749  return pseudoStyle->fontMetrics();
1750  }
1751  }
1752  return style()->fontMetrics();
1753 }
1754 
1755 const Font *RenderText::htmlFont(bool firstLine) const
1756 {
1757  const Font *f = nullptr;
1758  if (firstLine && hasFirstLine()) {
1759  RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE);
1760  if (pseudoStyle) {
1761  f = &pseudoStyle->htmlFont();
1762  }
1763  } else {
1764  f = &style()->htmlFont();
1765  }
1766  return f;
1767 }
1768 
1769 bool RenderText::containsOnlyWhitespace(unsigned int from, unsigned int len) const
1770 {
1771  unsigned int currPos;
1772  for (currPos = from;
1773  currPos < from + len && (str->s[currPos] == '\n' || str->s[currPos].direction() == QChar::DirWS);
1774  currPos++) {};
1775  return currPos >= (from + len);
1776 }
1777 
1778 void RenderText::trimmedMinMaxWidth(int &beginMinW, bool &beginWS,
1779  int &endMinW, bool &endWS,
1780  bool &hasBreakableChar, bool &hasBreak,
1781  int &beginMaxW, int &endMaxW,
1782  int &minW, int &maxW, bool &stripFrontSpaces)
1783 {
1784  bool preserveWS = style()->preserveWS();
1785  bool preserveLF = style()->preserveLF();
1786  bool autoWrap = style()->autoWrap();
1787  if (preserveWS) {
1788  stripFrontSpaces = false;
1789  }
1790 
1791  int len = str->l;
1792  if (len == 0 || (stripFrontSpaces && str->containsOnlyWhitespace())) {
1793  maxW = 0;
1794  hasBreak = false;
1795  return;
1796  }
1797 
1798  minW = m_minWidth;
1799  maxW = m_maxWidth;
1800  beginWS = stripFrontSpaces ? false : m_hasBeginWS;
1801  endWS = m_hasEndWS;
1802 
1803  beginMinW = m_beginMinWidth;
1804  endMinW = m_endMinWidth;
1805 
1806  hasBreakableChar = m_hasBreakableChar;
1807  hasBreak = m_hasBreak;
1808 
1809  if (stripFrontSpaces && (str->s[0].direction() == QChar::DirWS || (!preserveLF && str->s[0] == '\n'))) {
1810  const Font *f = htmlFont(false);
1811  QChar space[1]; space[0] = ' ';
1812  int spaceWidth = f->charWidth(space, 1, 0, m_isSimpleText);
1813  maxW -= spaceWidth;
1814  }
1815 
1816  stripFrontSpaces = !preserveWS && m_hasEndWS;
1817 
1818  if (!autoWrap) {
1819  minW = maxW;
1820  } else if (minW > maxW) {
1821  minW = maxW;
1822  }
1823 
1824  // Compute our max widths by scanning the string for newlines.
1825  if (hasBreak) {
1826  const Font *f = htmlFont(false);
1827  bool firstLine = true;
1828  beginMaxW = endMaxW = maxW;
1829  for (int i = 0; i < len; i++) {
1830  int linelen = 0;
1831  while (i + linelen < len && str->s[i + linelen] != '\n') {
1832  linelen++;
1833  }
1834 
1835  if (linelen) {
1836  endMaxW = f->width(str->s, str->l, i, linelen, m_isSimpleText);
1837  if (firstLine) {
1838  firstLine = false;
1839  beginMaxW = endMaxW;
1840  }
1841  i += linelen;
1842  } else if (firstLine) {
1843  beginMaxW = 0;
1844  firstLine = false;
1845  }
1846  if (i == len - 1)
1847  // A <pre> run that ends with a newline, as in, e.g.,
1848  // <pre>Some text\n\n<span>More text</pre>
1849  {
1850  endMaxW = 0;
1851  }
1852  }
1853  }
1854 }
1855 
1856 bool RenderText::isPointInsideSelection(int x, int y, const Selection &) const
1857 {
1858  RenderText *rt = const_cast<RenderText *>(this);
1859  SelectionState selstate = selectionState();
1860  if (selstate == SelectionInside) {
1861  return true;
1862  }
1863  if (selstate == SelectionNone) {
1864  return false;
1865  }
1866  if (!firstTextBox()) {
1867  return false;
1868  }
1869 
1870  int absx, absy;
1871  if (!rt->absolutePosition(absx, absy)) {
1872  return false;
1873  }
1874 
1875  int startPos, endPos, offset;
1876  rt->selectionStartEnd(startPos, endPos);
1877 
1878  if (selstate == SelectionEnd) {
1879  startPos = 0;
1880  }
1881  if (selstate == SelectionStart) {
1882  endPos = str->l;
1883  }
1884 
1885  SelPointState sps;
1886  DOM::NodeImpl *node;
1887  /*FindSelectionResult res = */rt->checkSelectionPoint(x, y, absx, absy, node, offset, sps);
1888 
1889  return offset >= startPos && offset < endPos;
1890 }
1891 
1892 #ifdef ENABLE_DUMP
1893 
1894 static QString quoteAndEscapeNonPrintables(const QString &s)
1895 {
1896  QString result;
1897  result += '"';
1898  for (int i = 0; i != s.length(); ++i) {
1899  QChar c = s.at(i);
1900  if (c == '\\') {
1901  result += "\\\\";
1902  } else if (c == '"') {
1903  result += "\\\"";
1904  } else {
1905  ushort u = c.unicode();
1906  if (u >= 0x20 && u < 0x7F) {
1907  result += c;
1908  } else {
1909  QString hex;
1910  hex.sprintf("\\x{%X}", u);
1911  result += hex;
1912  }
1913  }
1914  }
1915  result += '"';
1916  return result;
1917 }
1918 
1919 static void writeTextRun(QTextStream &ts, const RenderText &o, const InlineTextBox &run)
1920 {
1921  ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width << ": "
1922  << quoteAndEscapeNonPrintables(o.data().string().mid(run.m_start, run.m_len));
1923 }
1924 
1925 void RenderText::dump(QTextStream &stream, const QString &ind) const
1926 {
1927  RenderObject::dump(stream, ind);
1928 
1929  for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1930  stream << endl << ind << " ";
1931  writeTextRun(stream, *this, *box);
1932  }
1933 }
1934 #endif
1935 
1936 RenderTextFragment::RenderTextFragment(DOM::NodeImpl *_node, DOM::DOMStringImpl *_str,
1937  int startOffset, int endOffset)
1938  : RenderText(_node, _str->substring(startOffset, endOffset)),
1939  m_start(startOffset), m_end(endOffset), m_generatedContentStr(nullptr), m_firstLetter(nullptr)
1940 {}
1941 
1942 RenderTextFragment::RenderTextFragment(DOM::NodeImpl *_node, DOM::DOMStringImpl *_str)
1943  : RenderText(_node, _str), m_start(0), m_firstLetter(nullptr)
1944 {
1945  m_generatedContentStr = _str;
1946  if (_str) {
1947  _str->ref();
1948  m_end = _str->l;
1949  } else {
1950  m_end = 0;
1951  }
1952 }
1953 
1954 RenderTextFragment::~RenderTextFragment()
1955 {
1956  if (m_generatedContentStr) {
1957  m_generatedContentStr->deref();
1958  }
1959 }
1960 
1961 void RenderTextFragment::detach()
1962 {
1963  if (m_firstLetter) {
1964  m_firstLetter->detach();
1965  }
1966 
1967  RenderText::detach();
1968 }
1969 
1970 bool RenderTextFragment::isTextFragment() const
1971 {
1972  return true;
1973 }
1974 
1975 DOM::DOMStringImpl *RenderTextFragment::originalString() const
1976 {
1977  DOM::DOMStringImpl *result = nullptr;
1978  if (element()) {
1979  result = element()->string();
1980  } else {
1981  result = contentString();
1982  }
1983  if (result && (start() > 0 || start() < result->l)) {
1984  result = result->substring(start(), end());
1985  }
1986  return result;
1987 }
1988 
1989 void RenderTextFragment::setTextInternal(DOM::DOMStringImpl *text)
1990 {
1991  if (m_firstLetter) {
1992  m_firstLetter->detach();
1993  m_firstLetter = nullptr;
1994  }
1995  RenderText::setTextInternal(text);
1996 }
1997 
1998 #undef BIDI_DEBUG
1999 #undef DEBUG_LAYOUT
bool hasClipping() const const
int ascent() const const
bool end()
Separator_Space
const QFont & font() const const
This file is part of the HTML rendering engine for KDE.
bool hasSufficientContrast(const QColor &c1, const QColor &c2)
checks whether the given colors have enough contrast
Definition: helper.cpp:267
QColor retrieveBackgroundColor(const RenderObject *obj)
finds out the background color of an element
Definition: helper.cpp:248
void save()
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
QTextStream & endl(QTextStream &stream)
void setClipRegion(const QRegion &region, Qt::ClipOperation operation)
RightToLeft
QString & sprintf(const char *cformat,...)
DiffuseAlphaDither
QColor color() const const
void setFont(const QFont &font)
IntersectClip
QString fromRawData(const QChar *unicode, int size)
void append(const T &value)
bool isSpace() const const
QRegion clipRegion() const const
void setPen(const QColor &color)
ushort unicode() const const
void restore()
This library provides a full-featured HTML parser and widget.
const QList< QKeySequence > & end()
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
const QChar at(int position) const const
int height() const const
Base Class for all rendering tree objects.
int length() const const
int width(const QString &text, int len) const const
QTextStream & hex(QTextStream &stream)
bool begin(QPaintDevice *device)
const QPen & pen() const const
bool fixedPitch() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 16 2021 22:48:01 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.