📄 render_text.cpp
字号:
/**
* This file is part of the DOM implementation for KDE.
*
* (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
//#define DEBUG_LAYOUT
//#define BIDI_DEBUG
#include "rendering/render_canvas.h"
#include "rendering/render_object.h"
#include "rendering/render_text.h"
#include "rendering/break_lines.h"
#include "dom/dom2_range.h"
#include "xml/dom_nodeimpl.h"
#include "xml/dom_docimpl.h"
#include "xml/dom_position.h"
#include "render_arena.h"
#include "misc/loader.h"
#include "khtml_part.h"
#include <qpainter.h>
#include <kdebug.h>
#include <assert.h>
// You may have to turn this to 0 to compile without the headers for ICU installed.
#define HAVE_ICU_LIBRARY 0
#if HAVE_ICU_LIBRARY
#include <unicode/ubrk.h>
#include <unicode/uloc.h>
#include <unicode/utypes.h>
#include <unicode/parseerr.h>
#endif
using namespace khtml;
using namespace DOM;
#ifndef NDEBUG
static bool inInlineTextBoxDetach;
#endif
void InlineTextBox::detach(RenderArena* renderArena)
{
#ifndef NDEBUG
inInlineTextBoxDetach = true;
#endif
delete this;
#ifndef NDEBUG
inInlineTextBoxDetach = false;
#endif
// Recover the size left there for us by operator delete and free the memory.
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);
// Stash size where detach can find it.
*(size_t *)ptr = sz;
}
RenderText* InlineTextBox::textObject()
{
return static_cast<RenderText*>(m_object);
}
bool InlineTextBox::checkVerticalPoint(int _y, int _ty, int _h)
{
int topY = m_y;
int bottomY = m_y + m_height;
if (root()->hasSelectedChildren()) {
topY = kMin(root()->selectionTop(), topY);
bottomY = kMax(bottomY, root()->bottomOverflow());
}
if ((_ty + topY >= _y + _h) || (_ty + bottomY <= _y))
return false;
return true;
}
bool InlineTextBox::isSelected(int startPos, int endPos) const
{
int sPos = kMax(startPos - m_start, 0);
int ePos = kMin(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;
}
QRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos)
{
int sPos = kMax(startPos - m_start, 0);
int ePos = kMin(endPos - m_start, (int)m_len);
if (sPos >= ePos)
return QRect();
RootInlineBox* rootBox = root();
int selStart = m_reversed ? m_x + m_width : m_x;
int selEnd = selStart;
int selTop = rootBox->selectionTop();
int selHeight = rootBox->selectionHeight();
// FIXME: For justified text, just return the entire text box's rect. At the moment there's still no easy
// way to get the width of a run including the justification padding.
if (sPos > 0 && !m_toAdd) {
// The selection begins in the middle of our run.
int w = textObject()->width(m_start, sPos, m_firstLine);
if (m_reversed)
selStart -= w;
else
selStart += w;
}
if (m_toAdd || (sPos == 0 && ePos == m_len)) {
if (m_reversed)
selEnd = m_x;
else
selEnd = m_x + m_width;
}
else {
// Our run is partially selected, and so we have to actually do a measurement.
int w = textObject()->width(sPos + m_start, ePos - sPos, m_firstLine);
if (m_reversed)
selEnd = selStart - w;
else
selEnd = selStart + w;
}
int selLeft = m_reversed ? selEnd : selStart;
int selRight = m_reversed ? selStart : selEnd;
return QRect(selLeft + tx, selTop + ty, selRight - selLeft, selHeight);
}
void InlineTextBox::deleteLine(RenderArena* arena)
{
static_cast<RenderText*>(m_object)->removeTextBox(this);
detach(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;
// 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.
if (ltr) {
if (ellipsisX <= m_x) {
// Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
m_truncation = cFullTruncation;
foundBox = true;
return -1;
}
if (ellipsisX < m_x + m_width) {
if (m_reversed)
return -1; // FIXME: Support LTR truncation when the last run is RTL someday.
foundBox = true;
int offset = offsetForPosition(ellipsisX, false);
if (offset == 0) {
// No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start
// and the ellipsis edge.
m_truncation = cFullTruncation;
return kMin(ellipsisX, m_x);
}
// Set the truncation index on the text run. The ellipsis needs to be placed just after the last visible character.
m_truncation = offset + m_start;
return m_x + static_cast<RenderText*>(m_object)->width(m_start, offset, m_firstLine);
}
}
else {
// FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR)
}
return -1;
}
static int
simpleDifferenceBetweenColors(QColor c1, QColor c2)
{
// a distance could be computed by squaring the differences between components, but
// this is faster and so far seems good enough for our purposes.
return abs(c1.red() - c2.red()) + abs(c1.green() - c2.green()) + abs(c1.blue() - c2.blue());
}
static QColor
correctedTextColor(QColor textColor, QColor backgroundColor)
{
// Adjust the text color if it is too close to the background color,
// by darkening or lightening it to move it further away.
int d = simpleDifferenceBetweenColors(textColor, backgroundColor);
// semi-arbitrarily chose 255 value here after a few tests;
if (d > 255) {
return textColor;
}
int distanceFromWhite = simpleDifferenceBetweenColors(textColor, Qt::white);
int distanceFromBlack = simpleDifferenceBetweenColors(textColor, Qt::black);
if (distanceFromWhite < distanceFromBlack) {
return textColor.dark();
}
return textColor.light();
}
bool InlineTextBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty)
{
if (object()->isBR())
return false;
QRect 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 (object()->isBR() || !object()->shouldPaintWithinRoot(i) || object()->style()->visibility() != VISIBLE ||
m_truncation == cFullTruncation || i.phase == PaintActionOutline)
return;
int xPos = tx + m_x;
int w = width();
if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x()))
return;
bool isPrinting = (i.p->device()->devType() == QInternal::Printer);
// Determine whether or not we're selected.
bool haveSelection = !isPrinting && selectionState() != RenderObject::SelectionNone;
if (!haveSelection && i.phase == PaintActionSelection)
// When only painting the selection, don't bother to paint if there is none.
return;
// Determine whether or not we have marked text.
Range markedTextRange = KWQ(object()->document()->part())->markedTextRange();
bool haveMarkedText = markedTextRange.handle() != 0 && markedTextRange.startContainer() == object()->node();
bool markedTextUsesUnderlines = KWQ(object()->document()->part())->markedTextUsesUnderlines();
// Set our font.
RenderStyle* styleToUse = object()->style(m_firstLine);
int d = styleToUse->textDecorationsInEffect();
if (styleToUse->font() != i.p->font())
i.p->setFont(styleToUse->font());
const Font *font = &styleToUse->htmlFont();
// 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
// and marked text.
if ((haveSelection || haveMarkedText) && !markedTextUsesUnderlines && i.phase != PaintActionSelection && !isPrinting) {
if (haveMarkedText)
paintMarkedTextBackground(i.p, tx, ty, styleToUse, font, markedTextRange.startOffset(), markedTextRange.endOffset());
if (haveSelection)
paintSelection(i.p, tx, ty, styleToUse, font);
}
// 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
if (m_len <= 0) return;
QValueList<DocumentMarker> markers = object()->document()->markersForNode(object()->node());
QValueListIterator <DocumentMarker> markerIt = markers.begin();
QValueList<KWQKHTMLPart::MarkedTextUnderline> underlines;
if (haveMarkedText && markedTextUsesUnderlines) {
underlines = KWQ(object()->document()->part())->markedTextUnderlines();
}
QValueListIterator<KWQKHTMLPart::MarkedTextUnderline> underlineIt = underlines.begin();
QColor textColor = styleToUse->color();
// Make the text color legible against a white background
if (styleToUse->forceBackgroundsToWhite())
textColor = correctedTextColor(textColor, Qt::white);
if (textColor != i.p->pen().color())
i.p->setPen(textColor);
// Set a text shadow if we have one.
// FIXME: Support multiple shadow effects. Need more from the CG API before
// we can do this.
bool setShadow = false;
if (styleToUse->textShadow()) {
i.p->setShadow(styleToUse->textShadow()->x, styleToUse->textShadow()->y,
styleToUse->textShadow()->blur, styleToUse->textShadow()->color);
setShadow = true;
}
bool paintSelectedTextOnly = (i.phase == PaintActionSelection);
bool paintSelectedTextSeparately = false; // Whether or not we have to do multiple paints. Only
// necessary when a custom ::selection foreground color is applied.
QColor selectionColor = i.p->pen().color();
ShadowData* selectionTextShadow = 0;
if (haveSelection) {
RenderStyle* pseudoStyle = object()->getPseudoStyle(RenderStyle::SELECTION);
if (pseudoStyle) {
if (pseudoStyle->color() != selectionColor || pseudoStyle->textShadow()) {
if (!paintSelectedTextOnly)
paintSelectedTextSeparately = true;
if (pseudoStyle->color() != selectionColor)
selectionColor = pseudoStyle->color();
if (pseudoStyle->textShadow())
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -