📄 rendertext.cpp
字号:
/* * (C) 1999 Lars Knoll (knoll@kde.org) * (C) 2000 Dirk Mueller (mueller@kde.org) * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com) * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */#include "config.h"#include "RenderText.h"#include "CharacterNames.h"#include "FloatQuad.h"#include "FrameView.h"#include "InlineTextBox.h"#include "Range.h"#include "RenderArena.h"#include "RenderBlock.h"#include "RenderLayer.h"#include "RenderView.h"#include "Text.h"#include "TextBreakIterator.h"#include "VisiblePosition.h"#include "break_lines.h"#include <wtf/AlwaysInline.h>using namespace std;using namespace WTF;using namespace Unicode;namespace WebCore {// FIXME: Move to StringImpl.h eventually.static inline bool charactersAreAllASCII(StringImpl* text){ return charactersAreAllASCII(text->characters(), text->length());}RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str) : RenderObject(node) , m_text(str) , m_firstTextBox(0) , m_lastTextBox(0) , m_minWidth(-1) , m_maxWidth(-1) , m_beginMinWidth(0) , m_endMinWidth(0) , m_hasTab(false) , m_linesDirty(false) , m_containsReversedText(false) , m_isAllASCII(charactersAreAllASCII(m_text.get())){ ASSERT(m_text); setIsText(); m_text = document()->displayStringModifiedByEncoding(PassRefPtr<StringImpl>(m_text)); view()->frameView()->setIsVisuallyNonEmpty();}#ifndef NDEBUGRenderText::~RenderText(){ ASSERT(!m_firstTextBox); ASSERT(!m_lastTextBox);}#endifconst char* RenderText::renderName() const{ return "RenderText";}bool RenderText::isTextFragment() const{ return false;}bool RenderText::isWordBreak() const{ return false;}void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle){ // There is no need to ever schedule repaints from a style change of a text run, since // we already did this for the parent of the text run. // We do have to schedule layouts, though, since a style change can force us to // need to relayout. if (diff == StyleDifferenceLayout) setNeedsLayoutAndPrefWidthsRecalc(); ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE; ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE; if (oldTransform != style()->textTransform() || oldSecurity != style()->textSecurity()) { if (RefPtr<StringImpl> textToTransform = originalText()) setText(textToTransform.release(), true); }}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(); RenderObject::destroy();}void RenderText::extractTextBox(InlineTextBox* box){ checkConsistency(); 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(); checkConsistency();}void RenderText::attachTextBox(InlineTextBox* box){ checkConsistency(); 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; checkConsistency();}void RenderText::removeTextBox(InlineTextBox* box){ checkConsistency(); 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()); checkConsistency();}void RenderText::deleteTextBoxes(){ if (firstTextBox()) { RenderArena* arena = renderArena(); InlineTextBox* next; for (InlineTextBox* curr = firstTextBox(); curr; curr = next) { next = curr->nextTextBox(); curr->destroy(arena); } m_firstTextBox = m_lastTextBox = 0; }}PassRefPtr<StringImpl> RenderText::originalText() const{ Node* e = node(); return e ? static_cast<Text*>(e)->string() : 0;}void RenderText::absoluteRects(Vector<IntRect>& rects, int tx, int ty, bool){ for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) rects.append(IntRect(tx + box->x(), ty + box->y(), box->width(), box->height()));}void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, unsigned end, bool useSelectionHeight){ // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this // function to take ints causes various internal mismatches. But selectionRect takes ints, and // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX. ASSERT(end == UINT_MAX || end <= INT_MAX); ASSERT(start <= INT_MAX); start = min(start, static_cast<unsigned>(INT_MAX)); end = min(end, static_cast<unsigned>(INT_MAX)); FloatPoint absPos = localToAbsolute(FloatPoint()); for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { // Note: box->end() returns the index of the last character, not the index past it if (start <= box->start() && box->end() < end) { IntRect r = IntRect(absPos.x() + box->x(), absPos.y() + box->y(), box->width(), box->height()); if (useSelectionHeight) { IntRect selectionRect = box->selectionRect(absPos.x(), absPos.y(), start, end); r.setHeight(selectionRect.height()); r.setY(selectionRect.y()); } rects.append(r); } else { unsigned realEnd = min(box->end() + 1, end); IntRect r = box->selectionRect(absPos.x(), absPos.y(), start, realEnd); if (!r.isEmpty()) { if (!useSelectionHeight) { // change the height and y position because selectionRect uses selection-specific values r.setHeight(box->height()); r.setY(absPos.y() + box->y()); } rects.append(r); } } }}void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool){ for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) quads.append(localToAbsoluteQuad(FloatRect(box->x(), box->y(), box->width(), box->height())));}void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start, unsigned end, bool useSelectionHeight){ // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this // function to take ints causes various internal mismatches. But selectionRect takes ints, and // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX. ASSERT(end == UINT_MAX || end <= INT_MAX); ASSERT(start <= INT_MAX); start = min(start, static_cast<unsigned>(INT_MAX)); end = min(end, static_cast<unsigned>(INT_MAX)); for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { // Note: box->end() returns the index of the last character, not the index past it if (start <= box->start() && box->end() < end) { IntRect r = IntRect(box->x(), box->y(), box->width(), box->height()); if (useSelectionHeight) { IntRect selectionRect = box->selectionRect(0, 0, start, end); r.setHeight(selectionRect.height()); r.setY(selectionRect.y()); } quads.append(localToAbsoluteQuad(FloatRect(r))); } else { unsigned realEnd = min(box->end() + 1, end); IntRect r = box->selectionRect(0, 0, start, realEnd); if (!r.isEmpty()) { if (!useSelectionHeight) { // change the height and y position because selectionRect uses selection-specific values r.setHeight(box->height()); r.setY(box->y()); } quads.append(localToAbsoluteQuad(FloatRect(r))); } } }}InlineTextBox* RenderText::findNextInlineTextBox(int offset, int& pos) const{ // The text runs point to parts of the RenderText's m_text // (they don't include '\n') // Find the text run that includes the character at offset // and return pos, which is the position of the char in the run. if (!m_firstTextBox) return 0; InlineTextBox* s = m_firstTextBox; int off = s->len(); while (offset > off && s->nextTextBox()) { s = s->nextTextBox(); off = s->start() + s->len(); } // we are now in the correct text run pos = (offset > off ? s->len() : s->len() - (off - offset) ); return s;}VisiblePosition RenderText::positionForPoint(const IntPoint& point){ if (!firstTextBox() || textLength() == 0) return VisiblePosition(node(), 0, DOWNSTREAM); // Get the offset for the position, since this will take rtl text into account. int offset; // FIXME: We should be able to roll these special cases into the general cases in the loop below. if (firstTextBox() && point.y() < firstTextBox()->root()->bottomOverflow() && point.x() < firstTextBox()->m_x) { // at the y coordinate of the first line or above // and the x coordinate is to the left of the first text box left edge offset = firstTextBox()->offsetForPosition(point.x()); return VisiblePosition(node(), offset + firstTextBox()->start(), DOWNSTREAM); } if (lastTextBox() && point.y() >= lastTextBox()->root()->topOverflow() && point.x() >= lastTextBox()->m_x + lastTextBox()->m_width) { // at the y coordinate of the last line or below // and the x coordinate is to the right of the last text box right edge offset = lastTextBox()->offsetForPosition(point.x()); return VisiblePosition(node(), offset + lastTextBox()->start(), DOWNSTREAM); } InlineTextBox* lastBoxAbove = 0; for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { if (point.y() >= box->root()->topOverflow()) { int bottom = box->root()->nextRootBox() ? box->root()->nextRootBox()->topOverflow() : box->root()->bottomOverflow(); if (point.y() < bottom) { offset = box->offsetForPosition(point.x()); if (point.x() == box->m_x) // the x coordinate is equal to the left edge of this box // the affinity must be downstream so the position doesn't jump back to the previous line return VisiblePosition(node(), offset + box->start(), DOWNSTREAM); if (point.x() < box->m_x + box->m_width) // and the x coordinate is to the left of the right edge of this box // check to see if position goes in this box return VisiblePosition(node(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); if (!box->prevOnLine() && point.x() < box->m_x) // box is first on line // and the x coordinate is to the left of the first text box left edge return VisiblePosition(node(), offset + box->start(), DOWNSTREAM); if (!box->nextOnLine()) // box is last on line // and the x coordinate is to the right of the last text box right edge // generate VisiblePosition, use UPSTREAM affinity if possible return VisiblePosition(node(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); } lastBoxAbove = box; } } return VisiblePosition(node(), lastBoxAbove ? lastBoxAbove->start() + lastBoxAbove->len() : 0, DOWNSTREAM);}IntRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, int* extraWidthToEndOfLine){ if (!inlineBox) return IntRect(); ASSERT(inlineBox->isInlineTextBox()); if (!inlineBox->isInlineTextBox()) return IntRect(); InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox); int height = box->root()->bottomOverflow() - box->root()->topOverflow(); int top = box->root()->topOverflow(); int left = box->positionForOffset(caretOffset); int rootLeft = box->root()->x(); // FIXME: should we use the width of the root inline box or the // width of the containing block for this? if (extraWidthToEndOfLine) *extraWidthToEndOfLine = (box->root()->width() + rootLeft) - (left + 1); RenderBlock* cb = containingBlock(); if (style()->autoWrap()) { int availableWidth = cb->lineWidth(top, false); if (box->direction() == LTR) left = min(left, rootLeft + availableWidth - 1); else left = max(left, rootLeft); } return IntRect(left, top, caretWidth, height);}ALWAYS_INLINE int RenderText::widthFromCache(const Font& f, int start, int len, int xPos) const{ if (f.isFixedPitch() && !f.isSmallCaps() && m_isAllASCII) { int monospaceCharacterWidth = f.spaceWidth(); int tabWidth = allowTabs() ? monospaceCharacterWidth * 8 : 0; int w = 0; bool isSpace; bool previousCharWasSpace = true; // FIXME: Preserves historical behavior, but seems wrong for start > 0. for (int i = start; i < start + len; i++) { char c = (*m_text)[i]; if (c <= ' ') { if (c == ' ' || c == '\n') { w += monospaceCharacterWidth; isSpace = true; } else if (c == '\t') { w += tabWidth ? tabWidth - ((xPos + w) % tabWidth) : monospaceCharacterWidth; isSpace = true; } else isSpace = false; } else { w += monospaceCharacterWidth; isSpace = false; } if (isSpace && !previousCharWasSpace) w += f.wordSpacing(); previousCharWasSpace = isSpace; } return w; } return f.width(TextRun(text()->characters() + start, len, allowTabs(), xPos));}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -