#include "config.h"
#include "RenderText.h"
#include "DeprecatedString.h"
#include "InlineTextBox.h"
#include "Pen.h"
#include "Range.h"
#include "RenderArena.h"
#include "RenderBlock.h"
#include "break_lines.h"
#include <unicode/ubrk.h>
#include <wtf/AlwaysInline.h>
#include "Document.h"
#include "Frame.h"
using namespace std;
namespace WebCore {
bool RenderText::shouldSecureLastCharacter = true;
UBreakIterator* characterBreakIterator(const StringImpl* i)
{
if (!i)
return 0;
static bool createdIterator = false;
static UBreakIterator* iterator;
UErrorCode status;
if (!createdIterator) {
status = U_ZERO_ERROR;
iterator = ubrk_open(UBRK_CHARACTER, "en_us", 0, 0, &status);
createdIterator = true;
}
if (!iterator)
return 0;
status = U_ZERO_ERROR;
ubrk_setText(iterator, reinterpret_cast<const UChar*>(i->characters()), i->length(), &status);
if (status != U_ZERO_ERROR)
return 0;
return iterator;
}
int RenderText::previousOffset(int current) const
{
UBreakIterator* iterator = characterBreakIterator(str.get());
if (!iterator)
return current - 1;
long result = ubrk_preceding(iterator, current);
if (result == UBRK_DONE)
result = current - 1;
return result;
}
int RenderText::nextOffset(int current) const
{
UBreakIterator* iterator = characterBreakIterator(str.get());
if (!iterator)
return current + 1;
long result = ubrk_following(iterator, current);
if (result == UBRK_DONE)
result = current + 1;
return result;
}
RenderText::RenderText(Node* node, StringImpl *_str)
: RenderObject(node), str(_str), m_firstTextBox(0), m_lastTextBox(0)
, m_minWidth(-1), m_maxWidth(-1), m_selectionState(SelectionNone)
, m_linesDirty(false), m_containsReversedText(false)
, m_allAsciiChecked(false), m_allAscii(false)
, m_monospaceCharacterWidth(0)
, m_secureLastCharacterTimer(0)
, m_candidateComputedTextSize(0)
{
setRenderText();
if (str)
str = str->replace('\\', backslashAsCurrencySymbol());
ASSERT(!str || !str->length() || str->characters());
}
void RenderText::setStyle(RenderStyle *_style)
{
if ( style() != _style ) {
bool needToTransformText = (!style() && _style->textTransform() != TTNONE) ||
(style() && style()->textTransform() != _style->textTransform());
bool needToSecureText = (!style() && _style->textSecurity() != TSNONE);
RenderObject::setStyle( _style );
if (needToTransformText || needToSecureText) {
RefPtr<StringImpl> textToTransform = originalString();
if (textToTransform)
setText(textToTransform.get(), true);
}
else
cacheWidths();
}
}
void RenderText::destroy()
{
if (!documentBeingDestroyed()) {
if (firstTextBox()) {
if (isBR()) {
RootInlineBox* next = firstTextBox()->root()->nextRootBox();
if (next)
next->markDirty();
}
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
box->remove();
}
else if (parent())
parent()->dirtyLinesFromChangedChild(this);
}
deleteTextBoxes();
if (m_secureLastCharacterTimer) {
delete m_secureLastCharacterTimer;
m_secureLastCharacterTimer = 0;
}
RenderObject::destroy();
}
void RenderText::extractTextBox(InlineTextBox* box)
{
m_lastTextBox = box->prevTextBox();
if (box == m_firstTextBox)
m_firstTextBox = 0;
if (box->prevTextBox())
box->prevTextBox()->setNextLineBox(0);
box->setPreviousLineBox(0);
for (InlineRunBox* curr = box; curr; curr = curr->nextLineBox())
curr->setExtracted();
}
void RenderText::attachTextBox(InlineTextBox* box)
{
if (m_lastTextBox) {
m_lastTextBox->setNextLineBox(box);
box->setPreviousLineBox(m_lastTextBox);
}
else
m_firstTextBox = box;
InlineTextBox* last = box;
for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) {
curr->setExtracted(false);
last = curr;
}
m_lastTextBox = last;
}
void RenderText::removeTextBox(InlineTextBox* box)
{
if (box == m_firstTextBox)
m_firstTextBox = box->nextTextBox();
if (box == m_lastTextBox)
m_lastTextBox = box->prevTextBox();
if (box->nextTextBox())
box->nextTextBox()->setPreviousLineBox(box->prevTextBox());
if (box->prevTextBox())
box->prevTextBox()->setNextLineBox(box->nextTextBox());
}
void RenderText::deleteTextBoxes()
{
if (firstTextBox()) {
RenderArena* arena = renderArena();
InlineTextBox *curr = firstTextBox(), *next = 0;
while (curr) {
next = curr->nextTextBox();
curr->destroy(arena);
curr = next;
}
m_firstTextBox = m_lastTextBox = 0;
}
}
bool RenderText::isTextFragment() const
{
return false;
}
PassRefPtr<StringImpl> RenderText::originalString() const
{
return element() ? element()->string() : 0;
}
void RenderText::absoluteRects(DeprecatedValueList<IntRect>& rects, int _tx, int _ty)
{
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
rects.append(IntRect(_tx + box->xPos(),
_ty + box->yPos(),
box->width(),
box->height()));
}
DeprecatedValueList<IntRect> RenderText::lineBoxRects()
{
DeprecatedValueList<IntRect> rects;
int x = 0, y = 0;
absolutePositionForContent(x, y);
absoluteRects(rects, x, y);
return rects;
}
InlineTextBox* RenderText::findNextInlineTextBox(int offset, int &pos) const
{
if (!m_firstTextBox)
return 0;
InlineTextBox* s = m_firstTextBox;
int off = s->m_len;
while (offset > off && s->nextTextBox())
{
s = s->nextTextBox();
off = s->m_start + s->m_len;
}
pos = (offset > off ? s->m_len : s->m_len - (off - offset) );
return s;
}
VisiblePosition RenderText::positionForCoordinates(int x, int y)
{
if (!firstTextBox() || stringLength() == 0)
return VisiblePosition(element(), 0, DOWNSTREAM);
int absx, absy;
RenderBlock* cb = containingBlock();
cb->absolutePositionForContent(absx, absy);
if (cb->hasOverflowClip())
cb->layer()->subtractScrollOffset(absx, absy);
int offset;
if (firstTextBox() && y < absy + firstTextBox()->root()->bottomOverflow() && x < absx + firstTextBox()->m_x) {
offset = firstTextBox()->offsetForPosition(x - absx);
return VisiblePosition(element(), offset + firstTextBox()->m_start, DOWNSTREAM);
}
if (lastTextBox() && y >= absy + lastTextBox()->root()->topOverflow() && x >= absx + lastTextBox()->m_x + lastTextBox()->m_width) {
offset = lastTextBox()->offsetForPosition(x - absx);
return VisiblePosition(element(), offset + lastTextBox()->m_start, DOWNSTREAM);
}
InlineTextBox* lastBoxAbove = 0;
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
if (y >= absy + box->root()->topOverflow()) {
if (y < absy + box->root()->bottomOverflow()) {
offset = box->offsetForPosition(x - absx);
if (x == absx + box->m_x)
return VisiblePosition(element(), offset + box->m_start, DOWNSTREAM);
if (x < absx + box->m_x + box->m_width) {
int half = absx + box->m_x + box->m_width / 2;
EAffinity affinity = x < half ? DOWNSTREAM : VP_UPSTREAM_IF_POSSIBLE;
return VisiblePosition(element(), offset + box->m_start, affinity);
}
if (!box->prevOnLine() && x < absx + box->m_x)
return VisiblePosition(element(), offset + box->m_start, DOWNSTREAM);
if (!box->nextOnLine())
return VisiblePosition(element(), offset + box->m_start, VP_UPSTREAM_IF_POSSIBLE);
}
lastBoxAbove = box;
}
}
return VisiblePosition(element(), lastBoxAbove ? lastBoxAbove->m_start + lastBoxAbove->m_len : 0, DOWNSTREAM);
}
#if __GNUC__
static RenderObject *firstRendererOnNextLine(InlineBox *box) __attribute__ ((unused));
#endif
static RenderObject *firstRendererOnNextLine(InlineBox *box)
{
if (!box)
return 0;
RootInlineBox *root = box->root();
if (!root)
return 0;
if (root->endsWithBreak())
return 0;
RootInlineBox *nextRoot = root->nextRootBox();
if (!nextRoot)
return 0;
InlineBox *firstChild = nextRoot->firstChild();
if (!firstChild)
return 0;
return firstChild->object();
}
static RenderObject *lastRendererOnPrevLine(InlineBox *box)
{
if (!box)
return 0;
RootInlineBox *root = box->root();
if (!root)
return 0;
if (root->endsWithBreak())
return 0;
RootInlineBox *prevRoot = root->prevRootBox();
if (!prevRoot)
return 0;
InlineBox *lastChild = prevRoot->lastChild();
if (!lastChild)
return 0;
return lastChild->object();
}
static inline bool atLineWrap(InlineTextBox* box, int offset)
{
return box->nextTextBox() && !box->nextOnLine() && offset == box->m_start + box->m_len;
}
IntRect RenderText::caretRect(int offset, EAffinity affinity, int *extraWidthToEndOfLine)
{
if (!firstTextBox() || stringLength() == 0)
return IntRect();
InlineTextBox *box = 0;
for (box = firstTextBox(); box; box = box->nextTextBox()) {
if (box->containsCaretOffset(offset)) {
if (atLineWrap(box, offset) && affinity == DOWNSTREAM) {
box = box->nextTextBox();
offset = box->m_start;
} else {
InlineTextBox *prevBox = box->prevTextBox();
if (offset == box->m_start && affinity == UPSTREAM && prevBox && !box->prevOnLine()) {
if (prevBox) {
box = prevBox;
offset = box->m_start + box->m_len;
} else {
RenderObject *object = lastRendererOnPrevLine(box);
if (object)
return object->caretRect(0, affinity);
}
}
}
break;
}
}
if (!box) {
return IntRect();
}
int height = box->root()->bottomOverflow() - box->root()->topOverflow();
int top = box->root()->topOverflow();
int left = box->positionForOffset(offset);
int rootLeft = box->root()->xPos();
if (extraWidthToEndOfLine)
*extraWidthToEndOfLine = (box->root()->width() + rootLeft) - (left + 1);
int absx, absy;
absolutePositionForContent(absx, absy);
left += absx;
top += absy;
RenderBlock *cb = containingBlock();
if (style()->autoWrap()) {
int availableWidth = cb->lineWidth(top);
if (!box->m_reversed)
left = min(left, absx + rootLeft + availableWidth - 1);
else
left = max(left, absx + rootLeft);
}
return IntRect(left, top, 3, height);
}
void RenderText::posOfChar(int chr, int &x, int &y)
{
absolutePositionForContent(x, y);
int pos;
if (InlineTextBox* s = findNextInlineTextBox(chr, pos)) {
x += s->m_x; y += s->m_y;
}
}
bool RenderText::allAscii() const
{
if (m_allAsciiChecked)
return m_allAscii;
m_allAsciiChecked = true;
unsigned i;
for (i = 0; i < str->length(); i++)
if ((*str)[i] > 0x7f) {
m_allAscii = false;
return m_allAscii;
}
m_allAscii = true;
return m_allAscii;
}
bool RenderText::shouldUseMonospaceCache(const Font *f) const
{
return (f && f->isFixedPitch() && allAscii() && !style()->font().isSmallCaps());
}
void RenderText::cacheWidths()
{
const Font* f = font(false);
if (shouldUseMonospaceCache(f)) {
const UChar c = ' ';
m_monospaceCharacterWidth = f->width(TextRun(&c, 1));
} else {
m_monospaceCharacterWidth = 0;
}
}
ALWAYS_INLINE int RenderText::widthFromCache(const Font* f, int start, int len, int tabWidth, int xpos) const
{
if (m_monospaceCharacterWidth != 0) {
int i, w = 0;
for (i = start; i < start+len; i++) {
UChar c = (*str)[i];
UCharDirection dir = u_charDirection(c);
if (dir != U_DIR_NON_SPACING_MARK && dir != U_BOUNDARY_NEUTRAL) {
if (c == '\t' && tabWidth != 0)
w += tabWidth - ((xpos + w) % tabWidth);
else
w += m_monospaceCharacterWidth;
if (DeprecatedChar(c).isSpace() && i > start && !DeprecatedChar((*str)[i - 1]).isSpace())
w += f->wordSpacing();
}
}
return w;
}
return f->width(TextRun(string(), start, len, 0), TextStyle(tabWidth, xpos));
}
void RenderText::trimmedMinMaxWidth(int leadWidth,
int& beginMinW, bool& beginWS,
int& endMinW, bool& endWS,
bool& hasBreakableChar, bool& hasBreak,
int& beginMaxW, int& endMaxW,
int& minW, int& maxW, bool& stripFrontSpaces)
{
bool collapseWhiteSpace = style()->collapseWhiteSpace();
if (!collapseWhiteSpace)
stripFrontSpaces = false;
int len = str->length();
if (len == 0 || (stripFrontSpaces && str->containsOnlyWhitespace())) {
maxW = 0;
hasBreak = false;
return;
}
if (m_hasTab)
calcMinMaxWidth(leadWidth);
minW = m_minWidth;
maxW = m_maxWidth;
beginWS = !stripFrontSpaces && m_hasBeginWS;
endWS = m_hasEndWS;
beginMinW = m_beginMinWidth;
endMinW = m_endMinWidth;
hasBreakableChar = m_hasBreakableChar;
hasBreak = m_hasBreak;
if (stripFrontSpaces && ((*str)[0] == ' ' || ((*str)[0] == '\n' && !style()->preserveNewline()) || (*str)[0] == '\t')) {
const Font *f = font(false); const UChar space = ' ';
int spaceWidth = f->width(TextRun(&space, 1));
maxW -= spaceWidth + f->wordSpacing();
}
stripFrontSpaces = collapseWhiteSpace && m_hasEndWS;
if (!style()->autoWrap() || minW > maxW)
minW = maxW;
if (hasBreak) {
const Font *f = font(false);
bool firstLine = true;
beginMaxW = endMaxW = maxW;
for (int i = 0; i < len; i++)
{
int linelen = 0;
while (i+linelen < len && (*str)[i+linelen] != '\n')
linelen++;
if (linelen)
{
endMaxW = widthFromCache(f, i, linelen, tabWidth(), leadWidth + endMaxW);
if (firstLine) {
firstLine = false;
leadWidth = 0;
beginMaxW = endMaxW;
}
i += linelen;
}
else if (firstLine) {
beginMaxW = 0;
firstLine = false;
leadWidth = 0;
}
if (i == len-1)
endMaxW = 0;
}
}
}
void RenderText::calcMinMaxWidth()
{
ASSERT( !minMaxKnown() );
calcMinMaxWidth(0);
}
void RenderText::calcMinMaxWidth(int leadWidth)
{
m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
m_maxWidth = 0;
if (isBR())
return;
int currMinWidth = 0;
int currMaxWidth = 0;
m_hasBreakableChar = m_hasBreak = m_hasTab = m_hasBeginWS = m_hasEndWS = false;
const Font* f = font(false);
int wordSpacing = style()->wordSpacing();
int len = str->length();
const UChar* txt = str->characters();
bool needsWordSpacing = false;
bool ignoringSpaces = false;
bool isSpace = false;
bool firstWord = true;
bool firstLine = true;
int nextBreakable = -1;
bool breakAll = (style()->wordBreak() == BreakAllWordBreak || style()->wordBreak() == BreakWordBreak) && style()->autoWrap();
for (int i = 0; i < len; i++) {
UChar c = txt[i];
bool previousCharacterIsSpace = isSpace;
bool isNewline = false;
if (c == '\n') {
if (style()->preserveNewline()) {
m_hasBreak = true;
isNewline = true;
isSpace = false;
} else
isSpace = true;
} else if (c == '\t') {
if (!style()->collapseWhiteSpace()) {
m_hasTab = true;
isSpace = false;
} else
isSpace = true;
} else {
isSpace = c == ' ';
}
if ((isSpace || isNewline) && i == 0)
m_hasBeginWS = true;
if ((isSpace || isNewline) && i == len-1)
m_hasEndWS = true;
if (!ignoringSpaces && style()->collapseWhiteSpace() && previousCharacterIsSpace && isSpace)
ignoringSpaces = true;
if (ignoringSpaces && !isSpace)
ignoringSpaces = false;
if (ignoringSpaces || c == SOFT_HYPHEN) {
continue;
}
bool hasBreak = breakAll || isBreakable(txt, i, len, nextBreakable);
int j = i;
while (c != '\n' && c != ' ' && c != '\t' && c != SOFT_HYPHEN) {
j++;
if (j == len)
break;
c = txt[j];
if (breakAll || isBreakable(txt, j, len, nextBreakable))
break;
}
int wordlen = j - i;
if (wordlen) {
int w = widthFromCache(f, i, wordlen, tabWidth(), leadWidth + currMaxWidth);
currMinWidth += w;
currMaxWidth += w;
bool isSpace = (j < len) && c == ' ';
bool isCollapsibleWhiteSpace = (j < len) && style()->isCollapsibleWhiteSpace(c);
if (j < len && style()->autoWrap())
m_hasBreakableChar = true;
if (wordSpacing && (isSpace || isCollapsibleWhiteSpace) && !containsOnlyWhitespace(j, len-j))
currMaxWidth += wordSpacing;
if (firstWord) {
firstWord = false;
if (hasBreak)
m_hasBreakableChar = true;
m_beginMinWidth = hasBreak ? 0 : w;
}
m_endMinWidth = w;
if (currMinWidth > m_minWidth) m_minWidth = currMinWidth;
currMinWidth = 0;
i += wordlen-1;
}
else {
if (style()->autoWrap() || isNewline)
m_hasBreakableChar = true;
if (currMinWidth > m_minWidth) m_minWidth = currMinWidth;
currMinWidth = 0;
if (isNewline) { if (firstLine) {
firstLine = false;
leadWidth = 0;
m_beginMinWidth = currMaxWidth;
}
if (currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
currMaxWidth = 0;
}
else
{
currMaxWidth += f->width(TextRun(txt + i, 1), TextStyle(tabWidth(), leadWidth + currMaxWidth));
needsWordSpacing = isSpace && !previousCharacterIsSpace && i == len-1;
}
}
}
if (needsWordSpacing && len > 1)
currMaxWidth += wordSpacing;
m_minWidth = max(currMinWidth, m_minWidth);
m_maxWidth = max(currMaxWidth, m_maxWidth);
if (!style()->autoWrap())
m_minWidth = m_maxWidth;
if (style()->whiteSpace() == PRE) {
if (firstLine)
m_beginMinWidth = m_maxWidth;
m_endMinWidth = currMaxWidth;
}
setMinMaxKnown();
}
bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const
{
unsigned currPos;
for (currPos = from;
currPos < from+len && ((*str)[currPos] == '\n' || (*str)[currPos] == ' ' || (*str)[currPos] == '\t');
currPos++);
return currPos >= (from+len);
}
int RenderText::minXPos() const
{
if (!m_firstTextBox) return 0;
int retval=6666666;
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
retval = min(retval, (int)box->m_x);
return retval;
}
int RenderText::xPos() const
{
return m_firstTextBox ? m_firstTextBox->m_x : 0;
}
int RenderText::yPos() const
{
return m_firstTextBox ? m_firstTextBox->m_y : 0;
}
const Font& RenderText::font()
{
return style()->font();
}
void RenderText::setSelectionState(SelectionState s)
{
InlineTextBox* box;
m_selectionState = s;
if (s == SelectionStart || s == SelectionEnd || s == SelectionBoth) {
int startPos, endPos;
selectionStartEnd(startPos, endPos);
if(selectionState() == SelectionStart) {
endPos = str->length();
if (startPos != 0 && startPos == endPos) {
startPos = endPos - 1;
}
} else if(selectionState() == SelectionEnd)
startPos = 0;
for (box = firstTextBox(); box; box = box->nextTextBox()) {
if (box->isSelected(startPos, endPos)) {
RootInlineBox* line = box->root();
if (line)
line->setHasSelectedChildren(true);
}
}
}
else {
for (box = firstTextBox(); box; box = box->nextTextBox()) {
RootInlineBox* line = box->root();
if (line)
line->setHasSelectedChildren(s == SelectionInside);
}
}
containingBlock()->setSelectionState(s);
}
void RenderText::setTextWithOffset(StringImpl *text, unsigned offset, unsigned len, bool force)
{
unsigned oldLen = str ? str->length() : 0;
unsigned newLen = text ? text->length() : 0;
int delta = newLen - oldLen;
unsigned end = len ? offset+len-1 : offset;
RootInlineBox* firstRootBox = 0;
RootInlineBox* lastRootBox = 0;
bool dirtiedLines = false;
for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
if (curr->end() < offset)
continue;
if (curr->start() > end) {
curr->offsetRun(delta);
RootInlineBox* root = curr->root();
if (!firstRootBox) {
firstRootBox = root;
if (!dirtiedLines) { firstRootBox->markDirty();
dirtiedLines = true;
}
}
lastRootBox = root;
}
else if (curr->end() >= offset && curr->end() <= end) {
curr->dirtyLineBoxes(); dirtiedLines = true;
}
else if (curr->start() <= offset && curr->end() >= end) {
curr->dirtyLineBoxes(); dirtiedLines = true;
}
else if (curr->start() <= end && curr->end() >= end) {
curr->dirtyLineBoxes(); dirtiedLines = true;
}
}
if (lastRootBox)
lastRootBox = lastRootBox->nextRootBox();
if (firstRootBox) {
RootInlineBox* prev = firstRootBox->prevRootBox();
if (prev)
firstRootBox = prev;
}
for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr = curr->nextRootBox()) {
if (curr->lineBreakObj() == this && curr->lineBreakPos() > end)
curr->setLineBreakPos(curr->lineBreakPos()+delta);
}
m_linesDirty = dirtiedLines;
setText(text, force);
}
#define BULLET_CHAR 0x2022
#define SQUARE_CHAR 0x25AA
#define CIRCLE_CHAR 0x25E6
void RenderText::setText(StringImpl *text, bool force)
{
if (m_secureLastCharacterTimer && m_secureLastCharacterTimer->isActive())
m_secureLastCharacterTimer->stop();
if (!text)
return;
if (!force && str == text)
return;
m_allAsciiChecked = false;
str = text;
if (str) {
str = str->replace('\\', backslashAsCurrencySymbol());
if (style()) {
switch (style()->textTransform()) {
case CAPITALIZE:
{
RenderObject* o;
UChar previous = ' ';
for (o = previousInPreOrder(); o && (o->isInlineFlow() || o->isText() && static_cast<RenderText*>(o)->string()->length() == 0); o = o->previousInPreOrder())
;
if (o && o->isText()) {
StringImpl* prevStr = static_cast<RenderText*>(o)->string();
previous = (*prevStr)[prevStr->length() - 1];
}
str = str->capitalize(previous);
}
break;
case UPPERCASE: str = str->upper(); break;
case LOWERCASE: str = str->lower(); break;
case NONE:
default:;
}
switch (style()->textSecurity())
{
case TSDISC:
str= str->secure(BULLET_CHAR, shouldSecureLastCharacter);
break;
case TSCIRCLE:
str= str->secure(CIRCLE_CHAR, shouldSecureLastCharacter);
break;
case TSSQUARE:
str= str->secure(SQUARE_CHAR, shouldSecureLastCharacter);
break;
case TSNONE:
break;
}
if (!shouldSecureLastCharacter)
{
if (!m_secureLastCharacterTimer)
m_secureLastCharacterTimer = new Timer<RenderText>(this, &RenderText::secureLastCharacter);
m_secureLastCharacterTimer->startOneShot(1.0);
}
}
}
cacheWidths();
ASSERT(!isBR() || (str->length() == 1 && (*str)[0] == '\n'));
ASSERT(!str->length() || str->characters());
setNeedsLayoutAndMinMaxRecalc();
}
void RenderText::secureLastCharacter(Timer<RenderText> * aTimer)
{
setText(str.get(), true);
}
int RenderText::height() const
{
int retval = 0;
if (firstTextBox())
retval = lastTextBox()->m_y + lastTextBox()->height() - firstTextBox()->m_y;
return retval;
}
short RenderText::lineHeight(bool firstLine, bool) const
{
return parent()->lineHeight(firstLine, true);
}
void RenderText::dirtyLineBoxes(bool fullLayout, bool)
{
if (fullLayout)
deleteTextBoxes();
else if (!m_linesDirty) {
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
box->dirtyLineBoxes();
}
m_linesDirty = false;
}
InlineBox* RenderText::createInlineBox(bool, bool isRootLineBox, bool)
{
ASSERT(!isRootLineBox);
InlineTextBox* textBox = new (renderArena()) InlineTextBox(this);
if (!m_firstTextBox)
m_firstTextBox = m_lastTextBox = textBox;
else {
m_lastTextBox->setNextLineBox(textBox);
textBox->setPreviousLineBox(m_lastTextBox);
m_lastTextBox = textBox;
}
return textBox;
}
void RenderText::position(InlineBox* box, int from, int len, bool reverse, bool override)
{
InlineTextBox *s = static_cast<InlineTextBox*>(box);
if (len == 0) {
s->remove();
s->destroy(renderArena());
m_firstTextBox = m_lastTextBox = 0;
return;
}
reverse = reverse && !style()->visuallyOrdered();
m_containsReversedText |= reverse;
s->m_reversed = reverse;
s->m_dirOverride = override || style()->visuallyOrdered();
s->m_start = from;
s->m_len = len;
}
unsigned int RenderText::width(unsigned int from, unsigned int len, int xpos, bool firstLine) const
{
if (from >= str->length())
return 0;
if (from + len > str->length())
len = str->length() - from;
const Font *f = font(firstLine);
return width(from, len, f, xpos);
}
unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f, int xpos) const
{
if (!str->characters() || from > str->length())
return 0;
if (from + len > str->length())
len = str->length() - from;
int w;
if (!style()->preserveNewline() && f == &style()->font() && from == 0 && len == str->length())
w = m_maxWidth;
else if (f == &style()->font())
w = widthFromCache(f, from, len, tabWidth(), xpos);
else
w = f->width(TextRun(string(), from, len, 0), TextStyle(tabWidth(), xpos));
return w;
}
int RenderText::width() const
{
int minx = 100000000;
int maxx = 0;
for (InlineTextBox* s = firstTextBox(); s; s = s->nextTextBox()) {
if(s->m_x < minx)
minx = s->m_x;
if(s->m_x + s->m_width > maxx)
maxx = s->m_x + s->m_width;
}
return max(0, maxx-minx);
}
IntRect RenderText::getAbsoluteRepaintRect()
{
RenderObject *cb = containingBlock();
return cb->getAbsoluteRepaintRect();
}
IntRect RenderText::selectionRect()
{
IntRect rect;
if (selectionState() == SelectionNone)
return rect;
RenderBlock* cb = containingBlock();
if (!cb)
return rect;
int startPos, endPos;
if (selectionState() == SelectionInside) {
startPos = 0;
endPos = str->length();
} else {
selectionStartEnd(startPos, endPos);
if (selectionState() == SelectionStart)
endPos = str->length();
else if (selectionState() == SelectionEnd)
startPos = 0;
}
if (startPos == endPos)
return rect;
int absx, absy;
cb->absolutePositionForContent(absx, absy);
RenderLayer* layer = cb->layer();
if (layer)
layer->subtractScrollOffset(absx, absy);
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
rect.unite(box->selectionRect(absx, absy, startPos, endPos));
return rect;
}
short RenderText::verticalPositionHint( bool firstLine ) const
{
return parent()->verticalPositionHint( firstLine );
}
const Font *RenderText::font(bool firstLine) const
{
return &style(firstLine)->font();
}
int RenderText::caretMinOffset() const
{
InlineTextBox *box = firstTextBox();
if (!box)
return 0;
int minOffset = box->m_start;
for (box = box->nextTextBox(); box; box = box->nextTextBox())
minOffset = min(minOffset, box->m_start);
return minOffset;
}
int RenderText::caretMaxOffset() const
{
InlineTextBox* box = lastTextBox();
if (!box)
return str->length();
int maxOffset = box->m_start + box->m_len;
for (box = box->prevTextBox(); box; box = box->prevTextBox())
maxOffset = max(maxOffset,box->m_start + box->m_len);
return maxOffset;
}
unsigned RenderText::caretMaxRenderedOffset() const
{
int l = 0;
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
l += box->m_len;
return l;
}
InlineBox* RenderText::inlineBox(int offset, EAffinity affinity)
{
for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
if (box->containsCaretOffset(offset)) {
if (atLineWrap(box, offset) && affinity == DOWNSTREAM)
return box->nextTextBox();
return box;
}
if (offset < box->m_start)
return box->prevTextBox() ? box->prevTextBox() : firstTextBox();
}
return 0;
}
}