📄 visible_text.cpp
字号:
/*
* Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "visible_text.h"
#include "misc/htmltags.h"
#include "rendering/render_text.h"
#include "xml/dom_nodeimpl.h"
#include "xml/dom_position.h"
#include "xml/dom2_rangeimpl.h"
#ifdef __OOM__
#include <allocs.h>
#endif
using DOM::DOMString;
using DOM::Node;
using DOM::NodeImpl;
using DOM::offsetInCharacters;
using DOM::Range;
using DOM::RangeImpl;
// FIXME: These classes should probably use the render tree and not the DOM tree, since elements could
// be hidden using CSS, or additional generated content could be added. For now, we just make sure
// text objects walk their renderers' InlineTextBox objects, so that we at least get the whitespace
// stripped out properly and obey CSS visibility for text runs.
namespace khtml {
const unsigned short nonBreakingSpace = 0xA0;
// Buffer that knows how to compare with a search target.
// Keeps enough of the previous text to be able to search in the future,
// but no more.
class CircularSearchBuffer
OOM_MODIFIED
{
public:
CircularSearchBuffer(const QString &target, bool isCaseSensitive);
~CircularSearchBuffer() { free(m_buffer); }
void clear() { m_cursor = m_buffer; m_bufferFull = false; }
void append(long length, const QChar *characters);
void append(const QChar &);
long neededCharacters() const;
bool isMatch() const;
long length() const { return m_target.length(); }
private:
QString m_target;
bool m_isCaseSensitive;
QChar *m_buffer;
QChar *m_cursor;
bool m_bufferFull;
CircularSearchBuffer(const CircularSearchBuffer&);
CircularSearchBuffer &operator=(const CircularSearchBuffer&);
};
TextIterator::TextIterator() : m_endContainer(0), m_endOffset(0), m_positionNode(0)
{
}
TextIterator::TextIterator(const Range &r, IteratorKind kind) : m_endContainer(0), m_endOffset(0), m_positionNode(0)
{
const RangeImpl *ri = r.handle();
if (!ri)
return;
int exceptionCode = 0;
// get and validate the range endpoints
NodeImpl *startContainer = ri->startContainer(exceptionCode);
long startOffset = ri->startOffset(exceptionCode);
NodeImpl *endContainer = ri->endContainer(exceptionCode);
long endOffset = ri->endOffset(exceptionCode);
if (exceptionCode != 0)
return;
// remember ending place - this does not change
m_endContainer = endContainer;
m_endOffset = endOffset;
// set up the current node for processing
m_node = ri->startNode();
if (m_node == 0)
return;
m_offset = m_node == startContainer ? startOffset : 0;
m_handledNode = false;
m_handledChildren = false;
// calculate first out of bounds node
m_pastEndNode = ri->pastEndNode();
// initialize node processing state
m_needAnotherNewline = false;
m_textBox = 0;
// initialize record of previous node processing
m_lastTextNode = 0;
m_lastTextNodeEndedWithCollapsedSpace = false;
if (kind == RUNFINDER)
m_lastCharacter = 0;
else
m_lastCharacter = '\n';
#ifndef NDEBUG
// need this just because of the assert in advance()
m_positionNode = m_node;
#endif
// identify the first run
advance();
}
void TextIterator::advance()
{
// otherwise, where are we advancing from?
assert(m_positionNode);
// reset the run information
m_positionNode = 0;
m_textLength = 0;
// handle remembered node that needed a newline after the text node's newline
if (m_needAnotherNewline) {
// emit the newline, with position a collapsed range at the end of current node.
emitCharacter('\n', m_node->parentNode(), m_node, 1, 1);
m_needAnotherNewline = false;
return;
}
// handle remembered text box
if (m_textBox) {
handleTextBox();
if (m_positionNode) {
return;
}
}
while (m_node && m_node != m_pastEndNode) {
// handle current node according to its type
if (!m_handledNode) {
RenderObject *renderer = m_node->renderer();
if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
// FIXME: What about CDATA_SECTION_NODE?
if (renderer->style()->visibility() == VISIBLE) {
m_handledNode = handleTextNode();
}
} else if (renderer && (renderer->isImage() || renderer->isWidget())) {
if (renderer->style()->visibility() == VISIBLE) {
m_handledNode = handleReplacedElement();
}
} else {
m_handledNode = handleNonTextNode();
}
if (m_positionNode) {
return;
}
}
// find a new current node to handle in depth-first manner,
// calling exitNode() as we come back thru a parent node
NodeImpl *next = m_handledChildren ? 0 : m_node->firstChild();
m_offset = 0;
if (!next) {
next = m_node->nextSibling();
if (!next) {
if (m_node->traverseNextNode() == m_pastEndNode)
break;
while (!next && m_node->parentNode()) {
m_node = m_node->parentNode();
exitNode();
if (m_positionNode) {
m_handledNode = true;
m_handledChildren = true;
return;
}
next = m_node->nextSibling();
}
}
}
// set the new current node
m_node = next;
m_handledNode = false;
m_handledChildren = false;
// how would this ever be?
if (m_positionNode) {
return;
}
}
}
bool TextIterator::handleTextNode()
{
m_lastTextNode = m_node;
RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
DOMString str = m_node->nodeValue();
// handle pre-formatted text
if (renderer->style()->whiteSpace() == khtml::PRE) {
long runStart = m_offset;
if (m_lastTextNodeEndedWithCollapsedSpace) {
emitCharacter(' ', m_node, 0, runStart, runStart);
return false;
}
long strLength = str.length();
long end = (m_node == m_endContainer) ? m_endOffset : LONG_MAX;
long runEnd = kMin(strLength, end);
m_positionNode = m_node;
m_positionOffsetBaseNode = 0;
m_positionStartOffset = runStart;
m_positionEndOffset = runEnd;
m_textCharacters = str.unicode() + runStart;
m_textLength = runEnd - runStart;
m_lastCharacter = str[runEnd - 1];
return true;
}
if (!renderer->firstTextBox() && str.length() > 0) {
m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space
return true;
}
m_textBox = renderer->firstTextBox();
handleTextBox();
return true;
}
void TextIterator::handleTextBox()
{
RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
DOMString str = m_node->nodeValue();
long start = m_offset;
long end = (m_node == m_endContainer) ? m_endOffset : LONG_MAX;
for (; m_textBox; m_textBox = m_textBox->nextTextBox()) {
long textBoxStart = m_textBox->m_start;
long runStart = kMax(textBoxStart, start);
// Check for collapsed space at the start of this run.
bool needSpace = m_lastTextNodeEndedWithCollapsedSpace
|| (m_textBox == renderer->firstTextBox() && textBoxStart == runStart && runStart > 0);
if (needSpace && !isCollapsibleWhitespace(m_lastCharacter) && !m_lastCharacter.isNull()) {
emitCharacter(' ', m_node, 0, runStart, runStart);
return;
}
long textBoxEnd = textBoxStart + m_textBox->m_len;
long runEnd = kMin(textBoxEnd, end);
if (runStart < runEnd) {
// Handle either a single newline character (which becomes a space),
// or a run of characters that does not include a newline.
// This effectively translates newlines to spaces without copying the text.
if (str[runStart] == '\n') {
emitCharacter(' ', m_node, 0, runStart, runStart + 1);
m_offset = runStart + 1;
} else {
long subrunEnd = str.find('\n', runStart);
if (subrunEnd == -1 || subrunEnd > runEnd) {
subrunEnd = runEnd;
}
m_offset = subrunEnd;
m_positionNode = m_node;
m_positionOffsetBaseNode = 0;
m_positionStartOffset = runStart;
m_positionEndOffset = subrunEnd;
m_textCharacters = str.unicode() + runStart;
m_textLength = subrunEnd - runStart;
m_lastTextNodeEndedWithCollapsedSpace = false;
m_lastCharacter = str[subrunEnd - 1];
}
// If we are doing a subrun that doesn't go to the end of the text box,
// come back again to finish handling this text box; don't advance to the next one.
if (m_positionEndOffset < textBoxEnd) {
return;
}
// Advance to the next text box.
InlineTextBox *nextTextBox = m_textBox->nextTextBox();
long nextRunStart = nextTextBox ? nextTextBox->m_start : str.length();
if (nextRunStart > runEnd) {
m_lastTextNodeEndedWithCollapsedSpace = true; // collapsed space between runs or at the end
}
m_textBox = nextTextBox;
return;
}
}
}
bool TextIterator::handleReplacedElement()
{
if (m_lastTextNodeEndedWithCollapsedSpace) {
emitCharacter(' ', m_lastTextNode->parentNode(), m_lastTextNode, 1, 1);
return false;
}
m_positionNode = m_node->parentNode();
m_positionOffsetBaseNode = m_node;
m_positionStartOffset = 0;
m_positionEndOffset = 1;
m_textCharacters = 0;
m_textLength = 0;
m_lastCharacter = 0;
return true;
}
bool TextIterator::handleNonTextNode()
{
switch (m_node->id()) {
case ID_BR: {
emitCharacter('\n', m_node->parentNode(), m_node, 0, 1);
break;
}
case ID_TD:
case ID_TH:
if (m_lastCharacter != '\n' && m_lastTextNode) {
emitCharacter('\t', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
}
break;
case ID_BLOCKQUOTE:
case ID_DD:
case ID_DIV:
case ID_DL:
case ID_DT:
case ID_H1:
case ID_H2:
case ID_H3:
case ID_H4:
case ID_H5:
case ID_H6:
case ID_HR:
case ID_LI:
case ID_OL:
case ID_P:
case ID_PRE:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -