#include "config.h"
#include "InlineTextBox.h"
#include "Document.h"
#include "Frame.h"
#include "GraphicsContext.h"
#include "RenderBlock.h"
#include "break_lines.h"
#include "Range.h"
#include "RenderArena.h"
#include <wtf/AlwaysInline.h>
#if PLATFORM(MAC)
#include "FrameMac.h"
#endif
using namespace std;
namespace WebCore {
#ifndef NDEBUG
static bool inInlineTextBoxDetach;
#endif
void InlineTextBox::destroy(RenderArena* renderArena)
{
#ifndef NDEBUG
inInlineTextBoxDetach = true;
#endif
delete this;
#ifndef NDEBUG
inInlineTextBoxDetach = false;
#endif
renderArena->free(*(size_t *)this, this);
}
void* InlineTextBox::operator new(size_t sz, RenderArena* renderArena) throw()
{
return renderArena->allocate(sz);
}
void InlineTextBox::operator delete(void* ptr, size_t sz)
{
assert(inInlineTextBoxDetach);
*static_cast<size_t*>(ptr) = sz;
}
bool InlineTextBox::isSelected(int startPos, int endPos) const
{
int sPos = max(startPos - m_start, 0);
int ePos = min(endPos - m_start, (int)m_len);
return (sPos < ePos);
}
RenderObject::SelectionState InlineTextBox::selectionState()
{
RenderObject::SelectionState state = object()->selectionState();
if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
int startPos, endPos;
object()->selectionStartEnd(startPos, endPos);
bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= m_start + m_len);
if (start && end)
state = RenderObject::SelectionBoth;
else if (start)
state = RenderObject::SelectionStart;
else if (end)
state = RenderObject::SelectionEnd;
else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
(state == RenderObject::SelectionStart || endPos > m_start + m_len))
state = RenderObject::SelectionInside;
}
return state;
}
IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos)
{
int sPos = max(startPos - m_start, 0);
int ePos = min(endPos - m_start, (int)m_len);
if (sPos >= ePos)
return IntRect();
RootInlineBox* rootBox = root();
RenderText* textObj = textObject();
int selTop = rootBox->selectionTop();
int selHeight = rootBox->selectionHeight();
const Font *f = textObj->font(m_firstLine);
IntRect r = enclosingIntRect(f->selectionRectForText(TextRun(textObj->string(), m_start, m_len, sPos, ePos),
TextStyle(textObj->tabWidth(), textPos(), m_toAdd, m_reversed, m_dirOverride),
IntPoint(tx + m_x, ty + selTop), selHeight));
if (r.x() > tx + m_x + m_width)
r.setWidth(0);
else if (r.right() - 1 > tx + m_x + m_width)
r.setWidth(tx + m_x + m_width - r.x());
return r;
}
void InlineTextBox::deleteLine(RenderArena* arena)
{
static_cast<RenderText*>(m_object)->removeTextBox(this);
destroy(arena);
}
void InlineTextBox::extractLine()
{
if (m_extracted)
return;
static_cast<RenderText*>(m_object)->extractTextBox(this);
}
void InlineTextBox::attachLine()
{
if (!m_extracted)
return;
static_cast<RenderText*>(m_object)->attachTextBox(this);
}
int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox)
{
if (foundBox) {
m_truncation = cFullTruncation;
return -1;
}
int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth;
if (ltr) {
if (ellipsisX <= m_x) {
m_truncation = cFullTruncation;
foundBox = true;
return -1;
}
if (ellipsisX < m_x + m_width) {
if (m_reversed)
return -1;
foundBox = true;
int offset = offsetForPosition(ellipsisX, false);
if (offset == 0) {
m_truncation = cFullTruncation;
return min(ellipsisX, m_x);
}
m_truncation = offset + m_start;
return m_x + static_cast<RenderText*>(m_object)->width(m_start, offset, textPos(), m_firstLine);
}
}
else {
}
return -1;
}
static Color
correctedTextColor(Color textColor, Color backgroundColor)
{
int d = differenceSquared(textColor, backgroundColor);
if (d > 65025) {
return textColor;
}
int distanceFromWhite = differenceSquared(textColor, Color::white);
int distanceFromBlack = differenceSquared(textColor, Color::black);
if (distanceFromWhite < distanceFromBlack) {
return textColor.dark();
}
return textColor.light();
}
bool InlineTextBox::isLineBreak() const
{
return object()->isBR() || (object()->style()->preserveNewline() && len() == 1 && (*textObject()->string())[start()] == '\n');
}
bool InlineTextBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty)
{
if (isLineBreak())
return false;
IntRect rect(tx + m_x, ty + m_y, m_width, m_height);
if (m_truncation != cFullTruncation && object()->style()->visibility() == VISIBLE && rect.contains(x, y)) {
object()->setInnerNode(i);
return true;
}
return false;
}
void InlineTextBox::paint(RenderObject::PaintInfo& i, int tx, int ty)
{
if (isLineBreak() || !object()->shouldPaintWithinRoot(i) || object()->style()->visibility() != VISIBLE ||
m_truncation == cFullTruncation || i.phase == PaintPhaseOutline)
return;
assert(i.phase != PaintPhaseSelfOutline && i.phase != PaintPhaseChildOutlines);
int xPos = tx + m_x - parent()->maxHorizontalShadow();
int w = width() + 2 * parent()->maxHorizontalShadow();
if (xPos >= i.r.right() || xPos + w <= i.r.x())
return;
bool isPrinting = textObject()->document()->printing();
bool haveSelection = !isPrinting && selectionState() != RenderObject::SelectionNone;
if (!haveSelection && i.phase == PaintPhaseSelection)
return;
Range *markedTextRange = object()->document()->frame()->markedTextRange();
int exception = 0;
bool haveMarkedText = markedTextRange && markedTextRange->startContainer(exception) == object()->node();
bool markedTextUsesUnderlines = object()->document()->frame()->markedTextUsesUnderlines();
RenderStyle* styleToUse = object()->style(m_firstLine);
int d = styleToUse->textDecorationsInEffect();
const Font* font = &styleToUse->font();
if (*font != i.p->font())
i.p->setFont(*font);
if (i.phase != PaintPhaseSelection && !isPrinting) {
#if PLATFORM(MAC)
if (styleToUse->highlight() != nullAtom && !i.p->paintingDisabled())
paintCustomHighlight(tx, ty, styleToUse->highlight());
#endif
if (haveMarkedText && !markedTextUsesUnderlines)
paintMarkedTextBackground(i.p, tx, ty, styleToUse, font, markedTextRange->startOffset(exception), markedTextRange->endOffset(exception));
if (haveSelection && !haveMarkedText)
paintSelection(i.p, tx, ty, styleToUse, font);
}
if (m_len <= 0) return;
DeprecatedValueList<MarkedTextUnderline> underlines;
if (haveMarkedText && markedTextUsesUnderlines) {
underlines = object()->document()->frame()->markedTextUnderlines();
}
DeprecatedValueListIterator<MarkedTextUnderline> underlineIt = underlines.begin();
Color textColor;
if (i.forceWhiteText)
textColor = Color::white;
else {
textColor = styleToUse->color();
if (styleToUse->forceBackgroundsToWhite())
textColor = correctedTextColor(textColor, Color::white);
}
if (textColor != i.p->pen().color())
i.p->setPen(textColor);
bool setShadow = false;
if (styleToUse->textShadow()) {
i.p->setShadow(IntSize(styleToUse->textShadow()->x, styleToUse->textShadow()->y),
styleToUse->textShadow()->blur, styleToUse->textShadow()->color);
setShadow = true;
}
bool paintSelectedTextOnly = (i.phase == PaintPhaseSelection);
bool paintSelectedTextSeparately = false; paintSelectedTextSeparately = haveMarkedText;
Color selectionColor = i.p->pen().color();
ShadowData* selectionTextShadow = 0;
if (haveSelection) {
Color foreground = object()->selectionForegroundColor();
if (foreground.isValid() && foreground != selectionColor) {
if (!paintSelectedTextOnly)
paintSelectedTextSeparately = true;
selectionColor = foreground;
}
RenderStyle* pseudoStyle = object()->getPseudoStyle(RenderStyle::SELECTION);
if (pseudoStyle && pseudoStyle->textShadow()) {
if (!paintSelectedTextOnly)
paintSelectedTextSeparately = true;
if (pseudoStyle->textShadow())
selectionTextShadow = pseudoStyle->textShadow();
}
}
StringImpl* textStr = textObject()->string();
TextStyle textStyle(textObject()->tabWidth(), textPos(), m_toAdd, m_reversed, m_dirOverride || styleToUse->visuallyOrdered());
if (!paintSelectedTextOnly && !paintSelectedTextSeparately) {
int endPoint = m_len;
if (m_truncation != cNoTruncation)
endPoint = m_truncation - m_start;
i.p->drawText(TextRun(textStr, m_start, endPoint), IntPoint(m_x + tx, m_y + ty + m_baseline), textStyle);
} else {
int sPos, ePos;
selectionStartEnd(sPos, ePos);
if (paintSelectedTextSeparately) {
if (sPos >= ePos)
i.p->drawText(TextRun(textStr, m_start, m_len), IntPoint(m_x + tx, m_y + ty + m_baseline), textStyle);
else {
if (sPos - 1 >= 0)
i.p->drawText(TextRun(textStr, m_start, m_len, 0, sPos), IntPoint(m_x + tx, m_y + ty + m_baseline), textStyle);
if (ePos < m_start + m_len)
i.p->drawText(TextRun(textStr, m_start, m_len, ePos), IntPoint(m_x + tx, m_y + ty + m_baseline), textStyle);
}
}
if (sPos < ePos) {
if (selectionColor != i.p->pen().color())
i.p->setPen(selectionColor);
if (selectionTextShadow)
i.p->setShadow(IntSize(selectionTextShadow->x, selectionTextShadow->y),
selectionTextShadow->blur,
selectionTextShadow->color);
i.p->save();
Color c = Color(255, 255, 255);
i.p->setPen(c);
i.p->drawText(TextRun(textStr, m_start, m_len, sPos, ePos), IntPoint(m_x + tx, m_y + ty + m_baseline), textStyle);
i.p->restore();
if (selectionTextShadow)
i.p->clearShadow();
}
}
if (d != TDNONE && i.phase != PaintPhaseSelection && styleToUse->htmlHacks()) {
i.p->setPen(styleToUse->color());
paintDecoration(i.p, tx, ty, d);
}
if (i.phase != PaintPhaseSelection) {
paintAllMarkersOfType(i.p, tx, ty, DocumentMarker::Spelling, styleToUse, font);
for ( ; underlineIt != underlines.end(); underlineIt++) {
MarkedTextUnderline underline = *underlineIt;
if (underline.endOffset <= start())
continue;
if (underline.startOffset <= end()) {
paintMarkedTextUnderline(i.p, tx, ty, underline);
if (underline.endOffset > end() + 1)
break;
} else
break;
}
}
if (setShadow)
i.p->clearShadow();
}
void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
{
int startPos, endPos;
if (object()->selectionState() == RenderObject::SelectionInside) {
startPos = 0;
endPos = textObject()->string()->length();
} else {
textObject()->selectionStartEnd(startPos, endPos);
if (object()->selectionState() == RenderObject::SelectionStart)
endPos = textObject()->string()->length();
else if (object()->selectionState() == RenderObject::SelectionEnd)
startPos = 0;
}
sPos = max(startPos - m_start, 0);
ePos = min(endPos - m_start, (int)m_len);
}
void InlineTextBox::paintSelection(GraphicsContext* p, int tx, int ty, RenderStyle* style, const Font* f)
{
return;
}
void InlineTextBox::paintMarkedTextBackground(GraphicsContext* p, int tx, int ty, RenderStyle* style, const Font* f, int startPos, int endPos)
{
int offset = m_start;
int sPos = max(startPos - offset, 0);
int ePos = min(endPos - offset, (int)m_len);
if (sPos >= ePos)
return;
p->save();
Color c = Color(42, 102, 223);
p->setPen(c);
RootInlineBox* r = root();
int y = r->selectionTop();
int h = r->selectionHeight();
p->drawHighlightForText(TextRun(textObject()->string(), m_start, m_len, sPos, ePos),
IntPoint(m_x + tx, y + ty), h,
TextStyle(textObject()->tabWidth(), textPos(), m_toAdd, m_reversed, m_dirOverride || style->visuallyOrdered()), c);
p->restore();
}
#if PLATFORM(MAC)
void InlineTextBox::paintCustomHighlight(int tx, int ty, const AtomicString& type)
{
RootInlineBox* r = root();
FloatRect rootRect(tx + r->xPos(), ty + r->selectionTop(), r->width(), r->selectionHeight());
FloatRect textRect(tx + xPos(), rootRect.y(), width(), rootRect.height());
Mac(object()->document()->frame())->paintCustomHighlight(type, textRect, rootRect, true, false);
}
#endif
void InlineTextBox::paintDecoration(GraphicsContext *pt, int _tx, int _ty, int deco)
{
_tx += m_x;
_ty += m_y;
if (m_truncation == cFullTruncation)
return;
int width = (m_truncation == cNoTruncation) ?
m_width : static_cast<RenderText*>(m_object)->width(m_start, m_truncation - m_start, textPos(), m_firstLine);
Color underline, overline, linethrough;
object()->getTextDecorationColors(deco, underline, overline, linethrough, true);
bool isPrinting = textObject()->document()->printing();
if (deco & UNDERLINE) {
pt->setPen(underline);
pt->drawLineForText(IntPoint(_tx, _ty), m_baseline, width, isPrinting);
}
if (deco & OVERLINE) {
pt->setPen(overline);
pt->drawLineForText(IntPoint(_tx, _ty), 0, width, isPrinting);
}
if (deco & LINE_THROUGH) {
pt->setPen(linethrough);
pt->drawLineForText(IntPoint(_tx, _ty), 2*m_baseline/3, width, isPrinting);
}
}
void InlineTextBox::paintSpellingMarker(GraphicsContext* pt, int _tx, int _ty, DocumentMarker marker)
{
_tx += m_x;
_ty += m_y;
if (m_truncation == cFullTruncation)
return;
int start = 0; int width = m_width; bool useWholeWidth = true;
unsigned paintStart = m_start;
unsigned paintEnd = end()+1; if (paintStart <= marker.startOffset) {
paintStart = marker.startOffset;
useWholeWidth = false;
start = static_cast<RenderText*>(m_object)->width(m_start, paintStart - m_start, textPos(), m_firstLine);
}
if (paintEnd != marker.endOffset) { paintEnd = min(paintEnd, marker.endOffset);
useWholeWidth = false;
}
if (m_truncation != cNoTruncation) {
paintEnd = min(paintEnd, (unsigned)m_truncation);
useWholeWidth = false;
}
if (!useWholeWidth) {
width = static_cast<RenderText*>(m_object)->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine);
}
int lineThickness = cMisspellingLineThickness;
int descent = m_height - m_baseline;
int underlineOffset;
if (descent <= (2 + lineThickness)) {
underlineOffset = m_height - lineThickness;
} else {
underlineOffset = m_baseline + 2;
}
pt->drawLineForMisspelling(IntPoint(_tx + start, _ty + underlineOffset), width);
}
void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, int _tx, int _ty, DocumentMarker marker, RenderStyle* style, const Font* f)
{
RootInlineBox* r = root();
int y = r->selectionTop();
int h = r->selectionHeight();
int sPos = max(marker.startOffset - m_start, (unsigned)0);
int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
TextRun run = TextRun(textObject()->string(), m_start, m_len, sPos, ePos);
TextStyle renderStyle = TextStyle(textObject()->tabWidth(), textPos(), m_toAdd, m_reversed, m_dirOverride || style->visuallyOrdered());
IntPoint startPoint = IntPoint(m_x + _tx, y + _ty);
IntRect markerRect = enclosingIntRect(f->selectionRectForText(run, renderStyle, startPoint, h));
object()->document()->setRenderedRectForMarker(object()->node(), marker, markerRect);
if (object()->document()->frame()->markedTextMatchesAreHighlighted()) {
Color yellow = Color(255, 255, 0);
pt->save();
pt->setPen(yellow); pt->addClip(IntRect(_tx + m_x, _ty + y, m_width, h));
pt->drawHighlightForText(run, startPoint, h, renderStyle, yellow);
pt->restore();
}
}
void InlineTextBox::paintAllMarkersOfType(GraphicsContext* pt, int _tx, int _ty, DocumentMarker::MarkerType markerType, RenderStyle* style, const Font* f)
{
Vector<DocumentMarker> markers = object()->document()->markersForNode(object()->node());
Vector<DocumentMarker>::iterator markerIt = markers.begin();
for ( ; markerIt != markers.end(); markerIt++) {
DocumentMarker marker = *markerIt;
if (marker.type != markerType)
continue;
if (marker.endOffset <= start())
continue;
if (marker.startOffset > end())
break;
switch (markerType) {
case DocumentMarker::Spelling:
paintSpellingMarker(pt, _tx, _ty, marker);
break;
case DocumentMarker::TextMatch:
paintTextMatchMarker(pt, _tx, _ty, marker, style, f);
break;
default:
assert(false);
}
if (marker.endOffset > end() + 1)
break;
}
}
void InlineTextBox::paintMarkedTextUnderline(GraphicsContext* pt, int _tx, int _ty, MarkedTextUnderline& underline)
{
_tx += m_x;
_ty += m_y;
if (m_truncation == cFullTruncation)
return;
int start = 0; int width = m_width; bool useWholeWidth = true;
unsigned paintStart = m_start;
unsigned paintEnd = end()+1; if (paintStart <= underline.startOffset) {
paintStart = underline.startOffset;
useWholeWidth = false;
start = static_cast<RenderText*>(m_object)->width(m_start, paintStart - m_start, textPos(), m_firstLine);
}
if (paintEnd != underline.endOffset) { paintEnd = min(paintEnd, (unsigned)underline.endOffset);
useWholeWidth = false;
}
if (m_truncation != cNoTruncation) {
paintEnd = min(paintEnd, (unsigned)m_truncation);
useWholeWidth = false;
}
if (!useWholeWidth) {
width = static_cast<RenderText*>(m_object)->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine);
}
int underlineOffset = m_height - 3;
pt->setPen(Pen(underline.color, underline.thick ? 2 : 0));
pt->drawLineForText(IntPoint(_tx + start, _ty), underlineOffset, width, textObject()->document()->printing());
}
int InlineTextBox::caretMinOffset() const
{
return m_start;
}
int InlineTextBox::caretMaxOffset() const
{
return m_start + m_len;
}
unsigned InlineTextBox::caretMaxRenderedOffset() const
{
return m_start + m_len;
}
int InlineTextBox::textPos() const
{
if (xPos() == 0)
return 0;
RenderBlock *blockElement = object()->containingBlock();
return m_reversed ? xPos() - blockElement->borderRight() - blockElement->paddingRight()
: xPos() - blockElement->borderLeft() - blockElement->paddingLeft();
}
int InlineTextBox::offsetForPosition(int _x, bool includePartialGlyphs) const
{
if (isLineBreak())
return 0;
RenderText* text = static_cast<RenderText*>(m_object);
RenderStyle *style = text->style(m_firstLine);
const Font* f = &style->font();
return f->offsetForPosition(TextRun(textObject()->string(), m_start, m_len),
TextStyle(text->tabWidth(), textPos(), m_toAdd, m_reversed, m_dirOverride || style->visuallyOrdered()),
_x - m_x, includePartialGlyphs);
}
int InlineTextBox::positionForOffset(int offset) const
{
if (isLineBreak())
return m_x;
RenderText *text = static_cast<RenderText *>(m_object);
const Font *f = text->font(m_firstLine);
int from = m_reversed ? offset - m_start : 0;
int to = m_reversed ? m_len : offset - m_start;
return enclosingIntRect(f->selectionRectForText(TextRun(text->string(), m_start, m_len, from, to),
TextStyle(text->tabWidth(), textPos(), m_toAdd, m_reversed, m_dirOverride),
IntPoint(m_x, 0), 0)).right();
}
bool InlineTextBox::containsCaretOffset(int offset) const
{
if (offset < m_start)
return false;
int pastEnd = m_start + m_len;
if (offset < pastEnd)
return true;
if (offset > pastEnd)
return false;
if (isLineBreak())
return false;
return true;
}
}