File indexing completed on 2024-12-08 12:19:53
0001 /** 0002 * This file is part of the DOM implementation for KDE. 0003 * 0004 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) 0005 * (C) 2000-2003 Dirk Mueller (mueller@kde.org) 0006 * (C) 2003, 2006 Apple Computer, Inc. 0007 * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) 0008 * (C) 2008 Germain Garand (germain@ebooksfrance.org) 0009 * 0010 * This library is free software; you can redistribute it and/or 0011 * modify it under the terms of the GNU Library General Public 0012 * License as published by the Free Software Foundation; either 0013 * version 2 of the License, or (at your option) any later version. 0014 * 0015 * This library is distributed in the hope that it will be useful, 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0018 * Library General Public License for more details. 0019 * 0020 * You should have received a copy of the GNU Library General Public License 0021 * along with this library; see the file COPYING.LIB. If not, write to 0022 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0023 * Boston, MA 02110-1301, USA. 0024 * 0025 */ 0026 0027 //#define DEBUG_LAYOUT 0028 //#define BIDI_DEBUG 0029 0030 #include "render_text.h" 0031 #include "render_canvas.h" 0032 #include "break_lines.h" 0033 #include "render_arena.h" 0034 #include "rendering/render_position.h" 0035 #include <xml/dom_nodeimpl.h> 0036 0037 #include <misc/loader.h> 0038 #include <misc/helper.h> 0039 0040 #include <QBitmap> 0041 #include <QImage> 0042 #include <QPainter> 0043 #include "khtml_debug.h" 0044 #include <limits.h> 0045 #include <math.h> 0046 0047 #include <config-khtml.h> 0048 0049 #if HAVE_ALLOCA_H 0050 // explicitly included for systems that don't provide it in stdlib.h or malloc.h 0051 # include <alloca.h> 0052 #else 0053 # if HAVE_MALLOC_H 0054 # include <malloc.h> 0055 # else 0056 # include <stdlib.h> 0057 # endif 0058 #endif 0059 0060 using namespace khtml; 0061 using namespace DOM; 0062 0063 #ifndef NDEBUG 0064 static bool inInlineTextBoxDetach; 0065 #endif 0066 0067 void InlineTextBox::detach(RenderArena *renderArena, bool noRemove) 0068 { 0069 if (!noRemove) { 0070 remove(); 0071 } 0072 0073 #ifndef NDEBUG 0074 inInlineTextBoxDetach = true; 0075 #endif 0076 delete this; 0077 #ifndef NDEBUG 0078 inInlineTextBoxDetach = false; 0079 #endif 0080 0081 // Recover the size left there for us by operator delete and free the memory. 0082 renderArena->free(*(size_t *)this, this); 0083 } 0084 0085 void *InlineTextBox::operator new(size_t sz, RenderArena *renderArena) throw() 0086 { 0087 return renderArena->allocate(sz); 0088 } 0089 0090 void InlineTextBox::operator delete(void *ptr, size_t sz) 0091 { 0092 assert(inInlineTextBoxDetach); 0093 0094 #ifdef KHTML_USE_ARENA_ALLOCATOR 0095 // Stash size where detach can find it. 0096 *(size_t *)ptr = sz; 0097 #endif 0098 } 0099 0100 void InlineTextBox::selectionStartEnd(int &sPos, int &ePos) 0101 { 0102 int startPos, endPos; 0103 if (object()->selectionState() == RenderObject::SelectionInside) { 0104 startPos = 0; 0105 endPos = renderText()->string()->l; 0106 } else { 0107 renderText()->selectionStartEnd(startPos, endPos); 0108 if (object()->selectionState() == RenderObject::SelectionStart) { 0109 endPos = renderText()->string()->l; 0110 } else if (object()->selectionState() == RenderObject::SelectionEnd) { 0111 startPos = 0; 0112 } 0113 } 0114 0115 sPos = qMax(startPos - m_start, 0); 0116 ePos = qMin(endPos - m_start, (int)m_len); 0117 } 0118 0119 RenderObject::SelectionState InlineTextBox::selectionState() 0120 { 0121 RenderObject::SelectionState state = object()->selectionState(); 0122 if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || 0123 state == RenderObject::SelectionBoth) { 0124 int startPos, endPos; 0125 renderText()->selectionStartEnd(startPos, endPos); 0126 0127 bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); 0128 bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= m_start + m_len); 0129 if (start && end) { 0130 state = RenderObject::SelectionBoth; 0131 } else if (start) { 0132 state = RenderObject::SelectionStart; 0133 } else if (end) { 0134 state = RenderObject::SelectionEnd; 0135 } else if ((state == RenderObject::SelectionEnd || startPos < m_start) && 0136 (state == RenderObject::SelectionStart || endPos > m_start + m_len)) { 0137 state = RenderObject::SelectionInside; 0138 } 0139 } 0140 return state; 0141 } 0142 0143 void InlineTextBox::paint(RenderObject::PaintInfo &i, int tx, int ty) 0144 { 0145 if (object()->isBR() || object()->style()->visibility() != VISIBLE || 0146 m_truncation == cFullTruncation || i.phase == PaintActionOutline) { 0147 return; 0148 } 0149 0150 if (i.phase == PaintActionSelection && object()->selectionState() == RenderObject::SelectionNone) 0151 // When only painting the selection, don't bother to paint if there is none. 0152 { 0153 return; 0154 } 0155 0156 int xPos = tx + m_x; 0157 int w = width(); 0158 if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x())) { 0159 return; 0160 } 0161 0162 // Set our font. 0163 RenderStyle *styleToUse = object()->style(m_firstLine); 0164 int d = styleToUse->textDecorationsInEffect(); 0165 if (styleToUse->font() != i.p->font()) { 0166 i.p->setFont(styleToUse->font()); 0167 } 0168 const Font *font = &styleToUse->htmlFont(); 0169 bool haveSelection = selectionState() != RenderObject::SelectionNone; 0170 0171 // Now calculate startPos and endPos, for painting selection. 0172 // We paint selection while endPos > 0 0173 int ePos = 0, sPos = 0; 0174 if (haveSelection && !object()->canvas()->staticMode()) { 0175 selectionStartEnd(sPos, ePos); 0176 } 0177 i.p->setPen(styleToUse->color()); 0178 0179 if (m_len > 0 && i.phase != PaintActionSelection) { 0180 int endPoint = m_len; 0181 if (m_truncation != cNoTruncation) { 0182 endPoint = m_truncation - m_start; 0183 } 0184 if (styleToUse->textShadow()) { 0185 paintShadow(i.p, font, tx, ty, styleToUse->textShadow()); 0186 } 0187 if (!haveSelection || sPos != 0 || ePos != m_len) { 0188 font->drawText(i.p, m_x + tx, m_y + ty + m_baseline, renderText()->string()->s, renderText()->string()->l, m_start, endPoint, 0189 m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight); 0190 } 0191 } 0192 0193 if (d != TDNONE && i.phase != PaintActionSelection && styleToUse->htmlHacks()) { 0194 i.p->setPen(styleToUse->color()); 0195 paintDecoration(i.p, font, tx, ty, d); 0196 } 0197 0198 if (haveSelection && i.phase == PaintActionSelection) { 0199 //qCDebug(KHTML_LOG) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos; 0200 if (sPos < ePos) { 0201 paintSelection(font, renderText(), i.p, styleToUse, tx, ty, sPos, ePos, d); 0202 } 0203 } 0204 } 0205 0206 /** returns the proper ::selection pseudo style for the given element 0207 * @return the style or 0 if no ::selection pseudo applies. 0208 */ 0209 inline const RenderStyle *retrieveSelectionPseudoStyle(const RenderObject *obj) 0210 { 0211 // https://www.w3.org/Style/CSS/Test/CSS3/Selectors/20021129/html/tests/css3-modsel-162.html 0212 // is of the opinion that ::selection of parent elements is also to be applied 0213 // to children, so let's do it. 0214 while (obj) { 0215 const RenderStyle *style = obj->style()->getPseudoStyle(RenderStyle::SELECTION); 0216 if (style) { 0217 return style; 0218 } 0219 0220 obj = obj->parent(); 0221 }/*wend*/ 0222 return nullptr; 0223 } 0224 0225 void InlineTextBox::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle *style, int tx, int ty, int startPos, int endPos, int deco) 0226 { 0227 if (startPos > m_len) { 0228 return; 0229 } 0230 if (startPos < 0) { 0231 startPos = 0; 0232 } 0233 0234 QColor hc; 0235 QColor hbg; 0236 const RenderStyle *pseudoStyle = retrieveSelectionPseudoStyle(text); 0237 if (pseudoStyle) { 0238 // ### support outline (mandated by CSS3) 0239 // ### support background-image? (optional by CSS3) 0240 if (pseudoStyle->backgroundColor().isValid()) { 0241 hbg = pseudoStyle->backgroundColor(); 0242 } 0243 hc = pseudoStyle->color(); 0244 } else { 0245 hc = style->palette().color(QPalette::Active, QPalette::HighlightedText); 0246 hbg = style->palette().color(QPalette::Active, QPalette::Highlight); 0247 // ### should be at most retrieved once per render text 0248 QColor bg = khtml::retrieveBackgroundColor(text); 0249 // It may happen that the contrast is -- well -- virtually non existent. 0250 // In this case, simply swap the colors, thus in compliance with 0251 // NN4 (win32 only), IE, and Mozilla. 0252 if (!khtml::hasSufficientContrast(hbg, bg)) { 0253 qSwap(hc, hbg); 0254 } 0255 } 0256 0257 p->setPen(hc); 0258 0259 //qCDebug(KHTML_LOG) << "textRun::painting(" << QString::fromRawData(text->str->s + m_start, m_len).left(30) << ") at(" << m_x+tx << "/" << m_y+ty << ")"; 0260 0261 const bool needClipping = startPos != 0 || endPos != m_len; 0262 0263 if (needClipping) { 0264 p->save(); 0265 0266 int visualSelectionStart = f->width(text->str->s, text->str->l, m_start, startPos, false, m_start, m_start + m_len, m_toAdd); 0267 int visualSelectionEnd = f->width(text->str->s, text->str->l, m_start, endPos, false, m_start, m_start + m_len, m_toAdd); 0268 int visualSelectionWidth = visualSelectionEnd - visualSelectionStart; 0269 if (m_reversed) { 0270 visualSelectionStart = f->width(text->str->s, text->str->l, m_start, m_len, false) - visualSelectionEnd; 0271 } 0272 0273 QRect selectionRect(m_x + tx + visualSelectionStart, m_y + ty, visualSelectionWidth, height()); 0274 QRegion r(selectionRect); 0275 if (p->hasClipping()) { 0276 r &= p->clipRegion(); 0277 } 0278 p->setClipRegion(r, Qt::IntersectClip); 0279 } 0280 0281 f->drawText(p, m_x + tx, m_y + ty + m_baseline, text->str->s, text->str->l, 0282 m_start, m_len, m_toAdd, 0283 m_reversed ? Qt::RightToLeft : Qt::LeftToRight, 0284 needClipping ? 0 : startPos, needClipping ? m_len : endPos, 0285 hbg, m_y + ty, height(), deco); 0286 0287 if (needClipping) { 0288 p->restore(); 0289 } 0290 } 0291 0292 void InlineTextBox::paintDecoration(QPainter *pt, const Font *f, int _tx, int _ty, int deco) 0293 { 0294 _tx += m_x; 0295 _ty += m_y; 0296 0297 if (m_truncation == cFullTruncation) { 0298 return; 0299 } 0300 0301 int width = m_width - 1; 0302 if (m_truncation != cNoTruncation) { 0303 width = static_cast<RenderText *>(m_object)->width(m_start, m_truncation - m_start, m_firstLine); 0304 } 0305 0306 RenderObject *p = object(); 0307 0308 QColor underline, overline, linethrough; 0309 p->getTextDecorationColors(deco, underline, overline, linethrough, p->style()->htmlHacks()); 0310 0311 if (deco & UNDERLINE) { 0312 pt->setPen(underline); 0313 f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::UNDERLINE); 0314 } 0315 if (deco & OVERLINE) { 0316 pt->setPen(overline); 0317 f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::OVERLINE); 0318 } 0319 if (deco & LINE_THROUGH) { 0320 pt->setPen(linethrough); 0321 f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::LINE_THROUGH); 0322 } 0323 // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to 0324 // support it. Lars 0325 } 0326 0327 void InlineTextBox::paintShadow(QPainter *pt, const Font *f, int _tx, int _ty, const ShadowData *shadow) 0328 { 0329 int x = m_x + _tx + shadow->x; 0330 int y = m_y + _ty + shadow->y; 0331 const RenderText *text = renderText(); 0332 0333 if (shadow->blur <= 0) { 0334 QColor c = pt->pen().color(); 0335 pt->setPen(shadow->color); 0336 f->drawText(pt, x, y + m_baseline, text->str->s, text->str->l, 0337 m_start, m_len, m_toAdd, 0338 m_reversed ? Qt::RightToLeft : Qt::LeftToRight); 0339 pt->setPen(c); 0340 0341 } else { 0342 const int thickness = shadow->blur; 0343 const int w = m_width + 2 * thickness; 0344 const int h = m_height + 2 * thickness; 0345 const QRgb color = shadow->color.rgba(); 0346 const int gray = qGray(color); 0347 const bool inverse = (gray < 100); 0348 const QRgb bgColor = (inverse) ? qRgb(255, 255, 255) : qRgb(0, 0, 0); 0349 QImage img(w, h, QImage::Format_RGB32); 0350 img.fill(bgColor); 0351 QPainter p; 0352 0353 p.begin(&img); 0354 p.setPen(shadow->color); 0355 p.setFont(pt->font()); 0356 f->drawText(&p, thickness, thickness + m_baseline, text->str->s, text->str->l, 0357 m_start, m_len, m_toAdd, 0358 m_reversed ? Qt::RightToLeft : Qt::LeftToRight); 0359 0360 p.end(); 0361 0362 int md = thickness * thickness; // max-dist^2 0363 0364 // blur map (division cache) 0365 float *bmap = (float *)alloca(sizeof(float) * (md + 1)); 0366 for (int n = 0; n <= md; n++) { 0367 float f; 0368 f = n / (float)(md + 1); 0369 f = 1.0 - f * f; 0370 bmap[n] = f; 0371 } 0372 0373 float factor = 0.0; // maximal potential opacity-sum 0374 for (int n = -thickness; n <= thickness; n++) 0375 for (int m = -thickness; m <= thickness; m++) { 0376 int d = n * n + m * m; 0377 if (d <= md) { 0378 factor += bmap[d]; 0379 } 0380 } 0381 0382 // arbitratry factor adjustment to make shadows solid. 0383 factor = factor / 1.333; 0384 0385 // alpha map 0386 float *amap = (float *)alloca(sizeof(float) * (h * w)); 0387 memset(amap, 0, h * w * (sizeof(float))); 0388 for (int j = thickness; j < h - thickness; j++) { 0389 const QRgb *line = (QRgb *)img.scanLine(j); 0390 for (int i = thickness; i < w - thickness; i++) { 0391 QRgb col = line[i]; 0392 if (col == bgColor) { 0393 continue; 0394 } 0395 float g = qGray(col); 0396 if (inverse) { 0397 g = (255 - g) / (255 - gray); 0398 } else { 0399 g = g / gray; 0400 } 0401 for (int n = -thickness; n <= thickness; n++) { 0402 for (int m = -thickness; m <= thickness; m++) { 0403 int d = n * n + m * m; 0404 if (d > md) { 0405 continue; 0406 } 0407 float f = bmap[d]; 0408 amap[(i + m) + (j + n)*w] += (g * f); 0409 } 0410 } 0411 } 0412 } 0413 0414 QImage res(w, h, QImage::Format_ARGB32); 0415 int r = qRed(color); 0416 int g = qGreen(color); 0417 int b = qBlue(color); 0418 0419 // divide by factor 0420 factor = 1.0 / factor; 0421 0422 for (int j = 0; j < h; j++) { 0423 QRgb *line = (QRgb *)res.scanLine(j); 0424 for (int i = 0; i < w; i++) { 0425 int a = (int)(amap[i + j * w] * factor * 255.0); 0426 if (a > 255) { 0427 a = 255; 0428 } 0429 line[i] = qRgba(r, g, b, a); 0430 } 0431 } 0432 0433 pt->drawImage(x - thickness, y - thickness, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither); 0434 } 0435 // Paint next shadow effect 0436 if (shadow->next) { 0437 paintShadow(pt, f, _tx, _ty, shadow->next); 0438 } 0439 } 0440 0441 /** 0442 * Distributes pixels to justify text. 0443 * @param numSpaces spaces left, will be decremented by one 0444 * @param toAdd number of pixels left to be distributed, will have the 0445 * amount of pixels distributed during this call subtracted. 0446 * @return number of pixels to distribute 0447 */ 0448 static inline int justifyWidth(int &numSpaces, int &toAdd) 0449 { 0450 int a = 0; 0451 if (numSpaces) { 0452 a = toAdd / numSpaces; 0453 toAdd -= a; 0454 numSpaces--; 0455 }/*end if*/ 0456 return a; 0457 } 0458 0459 FindSelectionResult InlineTextBox::checkSelectionPoint(int _x, int _y, int _tx, int _ty, int &offset) 0460 { 0461 // qCDebug(KHTML_LOG) << "InlineTextBox::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y 0462 // << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y; 0463 offset = 0; 0464 0465 if (_y < _ty + m_y) { 0466 return SelectionPointBefore; // above -> before 0467 } 0468 0469 if (_y > _ty + m_y + m_height) { 0470 // below -> after 0471 // Set the offset to the max 0472 offset = m_len; 0473 return SelectionPointAfter; 0474 } 0475 if (_x > _tx + m_x + m_width) { 0476 // to the right 0477 return SelectionPointAfterInLine; 0478 } 0479 0480 // The Y matches, check if we're on the left 0481 if (_x < _tx + m_x) { 0482 return SelectionPointBeforeInLine; 0483 } 0484 0485 // consider spacing for justified text 0486 int toAdd = m_toAdd; 0487 RenderText *text = static_cast<RenderText *>(object()); 0488 Q_ASSERT(text->isText()); 0489 bool justified = text->style()->textAlign() == JUSTIFY && toAdd > 0; 0490 int numSpaces = 0; 0491 if (justified) { 0492 for (int i = 0; i < m_len; i++) 0493 if (text->str->s[m_start + i].category() == QChar::Separator_Space) { 0494 numSpaces++; 0495 } 0496 0497 }/*end if*/ 0498 0499 int delta = _x - (_tx + m_x); 0500 //qCDebug(KHTML_LOG) << "InlineTextBox::checkSelectionPoint delta=" << delta; 0501 int pos = 0; 0502 const Font *f = text->htmlFont(m_firstLine); 0503 if (m_reversed) { 0504 delta -= m_width; 0505 while (pos < m_len) { 0506 int w = f->charWidth(text->str->s, text->str->l, m_start + pos, text->isSimpleText()); 0507 if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) { 0508 w += justifyWidth(numSpaces, toAdd); 0509 } 0510 int w2 = w / 2; 0511 w -= w2; 0512 delta += w2; 0513 if (delta >= 0) { 0514 break; 0515 } 0516 pos++; 0517 delta += w; 0518 } 0519 } else { 0520 while (pos < m_len) { 0521 int w = f->charWidth(text->str->s, text->str->l, m_start + pos, text->isSimpleText()); 0522 if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) { 0523 w += justifyWidth(numSpaces, toAdd); 0524 } 0525 int w2 = w / 2; 0526 w -= w2; 0527 delta -= w2; 0528 if (delta <= 0) { 0529 break; 0530 } 0531 pos++; 0532 delta -= w; 0533 } 0534 } 0535 // qCDebug(KHTML_LOG) << " Text --> inside at position " << pos; 0536 offset = pos; 0537 return SelectionPointInside; 0538 } 0539 0540 long InlineTextBox::caretMinOffset() const 0541 { 0542 return m_start; 0543 } 0544 0545 long InlineTextBox::caretMaxOffset() const 0546 { 0547 return m_start + m_len; 0548 } 0549 0550 unsigned long InlineTextBox::caretMaxRenderedOffset() const 0551 { 0552 return m_start + m_len; 0553 } 0554 0555 int InlineTextBox::offsetForPoint(int _x, int &ax) const 0556 { 0557 // Do binary search for finding out offset, saves some time for long 0558 // runs. 0559 int start = 0; 0560 int end = m_len; 0561 ax = m_x; 0562 int offset = (start + end) / 2; 0563 while (end - start > 0) { 0564 // always snap to the right column. This makes up for "jumpy" vertical 0565 // navigation. 0566 if (end - start == 1) { 0567 start = end; 0568 } 0569 0570 offset = (start + end) / 2; 0571 ax = m_x + widthFromStart(offset); 0572 if (ax > _x) { 0573 end = offset; 0574 } else if (ax < _x) { 0575 start = offset; 0576 } else { 0577 break; 0578 } 0579 } 0580 return m_start + offset; 0581 } 0582 0583 int InlineTextBox::widthFromStart(int pos) const 0584 { 0585 // gasp! sometimes pos is i < 0 which crashes Font::width 0586 // qCDebug(KHTML_LOG) << this << pos; 0587 pos = qMax(pos, 0); 0588 0589 const RenderText *t = renderText(); 0590 Q_ASSERT(t->isText()); 0591 const Font *f = t->htmlFont(m_firstLine); 0592 const QFontMetrics &fm = t->fontMetrics(m_firstLine); 0593 0594 int numSpaces = 0; 0595 // consider spacing for justified text 0596 bool justified = t->style()->textAlign() == JUSTIFY; 0597 //qCDebug(KHTML_LOG) << "InlineTextBox::width(int)"; 0598 if (justified && m_toAdd > 0) do { 0599 //qCDebug(KHTML_LOG) << "justify"; 0600 0601 // const QString cstr = QString::fromRawData(t->str->s + m_start, m_len); 0602 for (int i = 0; i < m_len; i++) 0603 if (t->str->s[m_start + i].category() == QChar::Separator_Space) { 0604 numSpaces++; 0605 } 0606 if (numSpaces == 0) { 0607 break; 0608 } 0609 0610 int toAdd = m_toAdd; 0611 int w = 0; // accumulated width 0612 int start = 0; // start of non-space sequence 0613 int current = 0; // current position 0614 while (current < pos) { 0615 // add spacing 0616 while (current < pos && t->str->s[m_start + current].category() == QChar::Separator_Space) { 0617 w += f->getWordSpacing(); 0618 w += f->getLetterSpacing(); 0619 w += justifyWidth(numSpaces, toAdd); 0620 w += fm.width(' '); // ### valid assumption? (LS) 0621 current++; start++; 0622 }/*wend*/ 0623 if (current >= pos) { 0624 break; 0625 } 0626 0627 // seek next space 0628 while (current < pos && t->str->s[m_start + current].category() != QChar::Separator_Space) { 0629 current++; 0630 } 0631 0632 // check run without spaces 0633 if (current > start) { 0634 w += f->width(t->str->s + m_start, m_len, start, current - start, false); 0635 start = current; 0636 } 0637 } 0638 0639 return w; 0640 0641 } while (false); /*end if*/ 0642 0643 //qCDebug(KHTML_LOG) << "default"; 0644 // else use existing width function 0645 // qCDebug(KHTML_LOG) << "result width:" << f->width(t->str->s + m_start, m_len, 0, pos, false); 0646 return f->width(t->str->s + m_start, m_len, 0, pos, false); 0647 0648 } 0649 0650 void InlineTextBox::deleteLine(RenderArena *arena) 0651 { 0652 static_cast<RenderText *>(m_object)->removeTextBox(this); 0653 detach(arena, true /*noRemove*/); 0654 } 0655 0656 void InlineTextBox::extractLine() 0657 { 0658 if (m_extracted) { 0659 return; 0660 } 0661 static_cast<RenderText *>(m_object)->extractTextBox(this); 0662 } 0663 0664 void InlineTextBox::attachLine() 0665 { 0666 if (!m_extracted) { 0667 return; 0668 } 0669 static_cast<RenderText *>(m_object)->attachTextBox(this); 0670 } 0671 0672 int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool &foundBox) 0673 { 0674 if (foundBox) { 0675 m_truncation = cFullTruncation; 0676 return -1; 0677 } 0678 0679 int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth; 0680 0681 // 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. 0682 if (ltr) { 0683 if (ellipsisX <= m_x) { 0684 // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box. 0685 m_truncation = cFullTruncation; 0686 foundBox = true; 0687 return -1; 0688 } 0689 0690 if (ellipsisX < m_x + m_width) { 0691 if (m_reversed) { 0692 return -1; // FIXME: Support LTR truncation when the last run is RTL someday. 0693 } 0694 0695 foundBox = true; 0696 0697 int ax; 0698 int offset = offsetForPoint(ellipsisX, ax) - 1; 0699 if (offset <= m_start) { 0700 // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start 0701 // and the ellipsis edge. 0702 m_truncation = cFullTruncation; 0703 return qMin(ellipsisX, (int)m_x); 0704 } 0705 0706 // Set the truncation index on the text run. The ellipsis needs to be placed just after the last visible character. 0707 m_truncation = offset; 0708 return widthFromStart(offset - m_start); 0709 } 0710 } else { 0711 // FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR) 0712 } 0713 return -1; 0714 } 0715 0716 // ----------------------------------------------------------------------------- 0717 0718 RenderText::RenderText(DOM::NodeImpl *node, DOMStringImpl *_str) 0719 : RenderObject(node) 0720 { 0721 // init RenderObject attributes 0722 setRenderText(); // our object inherits from RenderText 0723 0724 m_minWidth = -1; 0725 m_maxWidth = -1; 0726 str = _str; 0727 if (str) { 0728 str->ref(); 0729 } 0730 KHTMLAssert(!str || !str->l || str->s); 0731 0732 m_selectionState = SelectionNone; 0733 m_hasReturn = true; 0734 m_isSimpleText = false; 0735 m_firstTextBox = m_lastTextBox = nullptr; 0736 0737 #ifdef DEBUG_LAYOUT 0738 const QString cstr = QString::fromRawData(str->s, str->l); 0739 qCDebug(KHTML_LOG) << "RenderText ctr( " << cstr.length() << " ) '" << cstr << "'"; 0740 #endif 0741 } 0742 0743 void RenderText::setStyle(RenderStyle *_style) 0744 { 0745 if (style() != _style) { 0746 bool changedText = ((!style() && (_style->textTransform() != TTNONE || 0747 !_style->preserveLF() || !_style->preserveWS())) || 0748 (style() && (style()->textTransform() != _style->textTransform() || 0749 style()->whiteSpace() != _style->whiteSpace()))); 0750 0751 RenderObject::setStyle(_style); 0752 m_lineHeight = RenderObject::lineHeight(false); 0753 0754 if (!isBR() && changedText) { 0755 DOM::DOMStringImpl *textToTransform = originalString(); 0756 if (textToTransform) { 0757 setText(textToTransform, true); 0758 } 0759 } 0760 } 0761 } 0762 0763 RenderText::~RenderText() 0764 { 0765 if (str) { 0766 str->deref(); 0767 } 0768 assert(!m_firstTextBox); 0769 assert(!m_lastTextBox); 0770 } 0771 0772 void RenderText::detach() 0773 { 0774 if (!documentBeingDestroyed()) { 0775 if (firstTextBox()) { 0776 if (isBR()) { 0777 RootInlineBox *next = firstTextBox()->root()->nextRootBox(); 0778 if (next) { 0779 next->markDirty(); 0780 } 0781 } 0782 for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { 0783 box->remove(); 0784 } 0785 } else if (parent()) { 0786 parent()->dirtyLinesFromChangedChild(this); 0787 } 0788 } 0789 deleteInlineBoxes(); 0790 RenderObject::detach(); 0791 } 0792 0793 void RenderText::extractTextBox(InlineTextBox *box) 0794 { 0795 m_lastTextBox = box->prevTextBox(); 0796 if (box == m_firstTextBox) { 0797 m_firstTextBox = nullptr; 0798 } 0799 if (box->prevTextBox()) { 0800 box->prevTextBox()->setNextLineBox(nullptr); 0801 } 0802 box->setPreviousLineBox(nullptr); 0803 for (InlineRunBox *curr = box; curr; curr = curr->nextLineBox()) { 0804 curr->setExtracted(); 0805 } 0806 } 0807 0808 void RenderText::attachTextBox(InlineTextBox *box) 0809 { 0810 if (m_lastTextBox) { 0811 m_lastTextBox->setNextLineBox(box); 0812 box->setPreviousLineBox(m_lastTextBox); 0813 } else { 0814 m_firstTextBox = box; 0815 } 0816 InlineTextBox *last = box; 0817 for (InlineTextBox *curr = box; curr; curr = curr->nextTextBox()) { 0818 curr->setExtracted(false); 0819 last = curr; 0820 } 0821 m_lastTextBox = last; 0822 } 0823 0824 void RenderText::removeTextBox(InlineTextBox *box) 0825 { 0826 if (box == m_firstTextBox) { 0827 m_firstTextBox = box->nextTextBox(); 0828 } 0829 if (box == m_lastTextBox) { 0830 m_lastTextBox = box->prevTextBox(); 0831 } 0832 if (box->nextTextBox()) { 0833 box->nextTextBox()->setPreviousLineBox(box->prevTextBox()); 0834 } 0835 if (box->prevTextBox()) { 0836 box->prevTextBox()->setNextLineBox(box->nextTextBox()); 0837 } 0838 } 0839 0840 void RenderText::removeInlineBox(InlineBox *_box) 0841 { 0842 KHTMLAssert(_box->isInlineTextBox()); 0843 removeTextBox(static_cast<InlineTextBox *>(_box)); 0844 } 0845 0846 void RenderText::deleteInlineBoxes(RenderArena * /*arena*/) 0847 { 0848 if (firstTextBox()) { 0849 RenderArena *arena = renderArena(); 0850 InlineTextBox *next; 0851 for (InlineTextBox *curr = firstTextBox(); curr; curr = next) { 0852 next = curr->nextTextBox(); 0853 curr->detach(arena, true /*noRemove*/); 0854 } 0855 m_firstTextBox = m_lastTextBox = nullptr; 0856 } 0857 } 0858 0859 void RenderText::dirtyInlineBoxes(bool fullLayout, bool) 0860 { 0861 if (fullLayout) { 0862 deleteInlineBoxes(); 0863 } else { 0864 for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { 0865 box->dirtyInlineBoxes(); 0866 } 0867 } 0868 } 0869 0870 bool RenderText::isTextFragment() const 0871 { 0872 return false; 0873 } 0874 0875 DOM::DOMStringImpl *RenderText::originalString() const 0876 { 0877 return element() ? element()->string() : nullptr; 0878 } 0879 0880 const InlineTextBox *RenderText::findInlineTextBox(int offset, int &pos, bool checkFirstLetter) const 0881 { 0882 Q_UNUSED(checkFirstLetter); 0883 // The text boxes point to parts of the rendertext's str string 0884 // (they don't include '\n') 0885 // Find the text box that includes the character at @p offset 0886 // and return pos, which is the position of the char in the run. 0887 0888 if (!m_firstTextBox) { 0889 return nullptr; 0890 } 0891 0892 InlineTextBox *s = m_firstTextBox; 0893 int off = s->m_len; 0894 while (offset > off && s->nextTextBox()) { 0895 s = s->nextTextBox(); 0896 off = s->m_start + s->m_len; 0897 } 0898 // we are now in the correct text run 0899 if (offset >= s->m_start && offset < s->m_start + s->m_len) { 0900 pos = offset - s->m_start; 0901 } else { 0902 pos = (offset > off ? s->m_len : s->m_len - (off - offset)); 0903 } 0904 return s; 0905 } 0906 0907 bool RenderText::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction /*hitTestAction*/, bool /*inBox*/) 0908 { 0909 assert(parent()); 0910 0911 bool inside = false; 0912 if (style()->visibility() != HIDDEN) { 0913 for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) { 0914 if ((_y >= _ty + s->m_y) && (_y < _ty + s->m_y + s->m_height) && 0915 (_x >= _tx + s->m_x) && (_x < _tx + s->m_x + s->m_width)) { 0916 inside = true; 0917 break; 0918 } 0919 } 0920 } 0921 0922 // #### ported over from Safari. Can this happen at all? (lars) 0923 0924 if (inside && element()) { 0925 if (info.innerNode() && info.innerNode()->renderer() && 0926 !info.innerNode()->renderer()->isInline()) { 0927 // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node. 0928 info.setInnerNode(element()); 0929 0930 // Clear everything else. 0931 info.setInnerNonSharedNode(nullptr); 0932 info.setURLElement(nullptr); 0933 } 0934 0935 if (!info.innerNode()) { 0936 info.setInnerNode(element()); 0937 } 0938 0939 if (!info.innerNonSharedNode()) { 0940 info.setInnerNonSharedNode(element()); 0941 } 0942 } 0943 0944 return inside; 0945 } 0946 0947 FindSelectionResult RenderText::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl *&node, int &offset, SelPointState &) 0948 { 0949 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y 0950 // << " _tx=" << _tx << " _ty=" << _ty; 0951 //qCDebug(KHTML_LOG) << renderName() << "::checkSelectionPoint x=" << xPos() << " y=" << yPos() << " w=" << width() << " h=" << height(); 0952 0953 NodeImpl *lastNode = nullptr; 0954 int lastOffset = 0; 0955 FindSelectionResult lastResult = SelectionPointAfter; 0956 0957 for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) { 0958 FindSelectionResult result; 0959 // #### ? 0960 // result = s->checkSelectionPoint(_x, _y, _tx, _ty, offset); 0961 if (_y < _ty + s->m_y) { 0962 result = SelectionPointBefore; 0963 } else if (_y >= _ty + s->m_y + s->height()) { 0964 result = SelectionPointAfterInLine; 0965 } else if (_x < _tx + s->m_x) { 0966 result = SelectionPointBeforeInLine; 0967 } else if (_x >= _tx + s->m_x + s->width()) { 0968 result = SelectionPointAfterInLine; 0969 } else { 0970 int dummy; 0971 result = SelectionPointInside; 0972 // offsetForPoint shifts to the right: correct it 0973 offset = s->offsetForPoint(_x - _tx, dummy) - 1; 0974 } 0975 0976 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset; 0977 if (result == SelectionPointInside) { // x,y is inside the textrun 0978 // offset += s->m_start; // add the offset from the previous lines 0979 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint inside -> " << offset; 0980 node = element(); 0981 return SelectionPointInside; 0982 } else if (result == SelectionPointBefore) { 0983 if (!lastNode) { 0984 // x,y is before the textrun -> stop here 0985 offset = 0; 0986 // qCDebug(KHTML_LOG) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before"; 0987 node = element(); 0988 return SelectionPointBefore; 0989 } 0990 } else if (result == SelectionPointBeforeInLine) { 0991 offset = s->m_start; 0992 node = element(); 0993 return SelectionPointInside; 0994 } else if (result == SelectionPointAfterInLine) { 0995 lastOffset = s->m_start + s->m_len; 0996 lastNode = element(); 0997 lastResult = result; 0998 // no return here 0999 } 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 { 1719 QList<QRectF> list; 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