blob: 4523df8e42f54aa8c30e576396c06c2fa65409a8 [file] [log] [blame]
/**
* Copyright (C) 2011 Nokia Inc. All rights reserved.
*
* 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 "RenderQuote.h"
#include "Document.h"
#include "Element.h"
#include "HTMLElement.h"
#include "QuotesData.h"
#include "RenderStyle.h"
#include <algorithm>
#include <wtf/text/AtomicString.h>
#include <wtf/text/CString.h>
#define UNKNOWN_DEPTH -1
namespace WebCore {
static inline void adjustDepth(int &depth, QuoteType type)
{
switch (type) {
case OPEN_QUOTE:
case NO_OPEN_QUOTE:
++depth;
break;
case CLOSE_QUOTE:
case NO_CLOSE_QUOTE:
if (depth)
--depth;
break;
default:
ASSERT_NOT_REACHED();
}
}
RenderQuote::RenderQuote(Document* node, QuoteType quote)
: RenderText(node, StringImpl::empty())
, m_type(quote)
, m_depth(UNKNOWN_DEPTH)
, m_next(0)
, m_previous(0)
{
}
RenderQuote::~RenderQuote()
{
}
const char* RenderQuote::renderName() const
{
return "RenderQuote";
}
// This function places a list of quote renderers starting at "this" in the list of quote renderers already
// in the document's renderer tree.
// The assumptions are made (for performance):
// 1. The list of quotes already in the renderers tree of the document is already in a consistent state
// (All quote renderers are linked and have the correct depth set)
// 2. The quote renderers of the inserted list are in a tree of renderers of their own which has been just
// inserted in the main renderer tree with its root as child of some renderer.
// 3. The quote renderers in the inserted list have depths consistent with their position in the list relative
// to "this", thus if "this" does not need to change its depth upon insertion, the other renderers in the list don't
// need to either.
void RenderQuote::placeQuote()
{
RenderQuote* head = this;
ASSERT(!head->m_previous);
RenderQuote* tail = 0;
for (RenderObject* predecessor = head->previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) {
if (!predecessor->isQuote())
continue;
head->m_previous = toRenderQuote(predecessor);
if (head->m_previous->m_next) {
// We need to splice the list of quotes headed by head into the document's list of quotes.
tail = head;
while (tail->m_next)
tail = tail->m_next;
tail->m_next = head->m_previous->m_next;
ASSERT(tail->m_next->m_previous == head->m_previous);
tail->m_next->m_previous = tail;
tail = tail->m_next; // This marks the splicing point here there may be a depth discontinuity
}
head->m_previous->m_next = head;
ASSERT(head->m_previous->m_depth != UNKNOWN_DEPTH);
break;
}
int newDepth;
if (!head->m_previous) {
newDepth = 0;
goto skipNewDepthCalc;
}
newDepth = head->m_previous->m_depth;
do {
adjustDepth(newDepth, head->m_previous->m_type);
skipNewDepthCalc:
if (head->m_depth == newDepth) { // All remaining depth should be correct except if splicing was done.
if (!tail) // We've done the post splicing section already or there was no splicing.
break;
head = tail; // Continue after the splicing point
tail = 0; // Mark the possible splicing point discontinuity fixed.
newDepth = head->m_previous->m_depth;
continue;
}
head->m_depth = newDepth;
// FIXME: If the width and height of the quotation characters does not change we may only need to
// Invalidate the renderer's area not a relayout.
head->setNeedsLayoutAndPrefWidthsRecalc();
head = head->m_next;
if (head == tail) // We are at the splicing point
tail = 0; // Mark the possible depth discontinuity fixed.
} while (head);
}
#define ARRAY_SIZE(Carray) (sizeof(Carray) / sizeof(*Carray))
#define LANGUAGE_DATA(name, languageSourceArray) { name, languageSourceArray, ARRAY_SIZE(languageSourceArray) }
#define U(x) ((const UChar*)L##x)
static const UChar* simpleQuotes[] = {U("\""), U("\""), U("'"), U("'")};
static const UChar* englishQuotes[] = {U("\x201C"), U("\x201D"), U("\x2018"), U("\x2019")};
static const UChar* norwegianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x2039"), U("\x203A") };
static const UChar* romanianQuotes[] = { U("\x201E"), U("\x201D")};
static const UChar* russianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x201E"), U("\x201C") };
#undef U
struct LanguageData {
const char *name;
const UChar* const* const array;
const int arraySize;
bool operator<(const LanguageData& compareTo) const
{
return strcmp(name, compareTo.name);
}
};
// Data mast be alphabetically sorted and in all lower case for fast comparison
LanguageData languageData[] = {
LANGUAGE_DATA("en", englishQuotes),
LANGUAGE_DATA("no", norwegianQuotes),
LANGUAGE_DATA("ro", romanianQuotes),
LANGUAGE_DATA("ru", russianQuotes)
};
#undef LANGUAGE_DATA
const LanguageData* const languageDataEnd = languageData + ARRAY_SIZE(languageData);
#define defaultLanguageQuotesSource simpleQuotes
#define defaultLanguageQuotesCount ARRAY_SIZE(defaultLanguageQuotesSource)
static QuotesData* defaultLanguageQuotesValue = 0;
static const QuotesData* defaultLanguageQuotes()
{
if (!defaultLanguageQuotesValue) {
defaultLanguageQuotesValue = QuotesData::create(defaultLanguageQuotesCount);
if (!defaultLanguageQuotesValue)
return 0;
String* data = defaultLanguageQuotesValue->data();
for (size_t i = 0; i < defaultLanguageQuotesCount; ++i)
data[i] = defaultLanguageQuotesSource[i];
}
return defaultLanguageQuotesValue;
}
#undef defaultLanguageQuotesSource
#undef defaultLanguageQuotesCount
typedef HashMap<RefPtr<AtomicStringImpl>, QuotesData* > QuotesMap;
static QuotesMap& quotesMap()
{
DEFINE_STATIC_LOCAL(QuotesMap, staticQuotesMap, ());
return staticQuotesMap;
}
static const QuotesData* quotesForLanguage(AtomicStringImpl* language)
{
QuotesData* returnValue;
AtomicString lower(language->lower());
returnValue = quotesMap().get(lower.impl());
if (returnValue)
return returnValue;
CString s(static_cast<const String&>(lower).ascii());
LanguageData request = { s.buffer()->data(), 0, 0 };
const LanguageData* lowerBound = std::lower_bound<const LanguageData*, const LanguageData>(languageData, languageDataEnd, request);
if (lowerBound == languageDataEnd)
return defaultLanguageQuotes();
if (strncmp(lowerBound->name, request.name, strlen(lowerBound->name)))
return defaultLanguageQuotes();
returnValue = QuotesData::create(lowerBound->arraySize);
if (!returnValue)
return defaultLanguageQuotes();
String* data = returnValue->data();
for (int i = 0; i < lowerBound->arraySize; ++i)
data[i] = lowerBound->array[i];
quotesMap().set(lower.impl(), returnValue);
return returnValue;
}
#undef ARRAY_SIZE
static const QuotesData* defaultQuotes(const RenderObject* object)
{
DEFINE_STATIC_LOCAL(String, langString, ("lang"));
Node* node = object->generatingNode();
Element* element;
if (!node) {
element = object->document()->body();
if (!element)
element = object->document()->documentElement();
} else if (!node->isElementNode()) {
element = node->parentElement();
if (!element)
return defaultLanguageQuotes();
} else
element = toElement(node);
const AtomicString* language;
while ((language = &element->getAttribute(langString)) && language->isNull()) {
element = element->parentElement();
if (!element)
return defaultLanguageQuotes();
}
return quotesForLanguage(language->impl());
}
PassRefPtr<StringImpl> RenderQuote::originalText() const
{
if (!parent())
return 0;
ASSERT(m_depth != UNKNOWN_DEPTH);
const QuotesData* quotes = style()->quotes();
if (!quotes)
quotes = defaultQuotes(this);
if (!quotes->length)
return emptyAtom.impl();
int index = m_depth * 2;
switch (m_type) {
case NO_OPEN_QUOTE:
case NO_CLOSE_QUOTE:
return String("").impl();
case CLOSE_QUOTE:
if (index)
--index;
else
++index;
break;
case OPEN_QUOTE:
break;
default:
ASSERT_NOT_REACHED();
return emptyAtom.impl();
}
if (index >= quotes->length)
index = (quotes->length-2) | (index & 1);
if (index < 0)
return emptyAtom.impl();
return quotes->data()[index].impl();
}
void RenderQuote::computePreferredLogicalWidths(float lead)
{
setTextInternal(originalText());
RenderText::computePreferredLogicalWidths(lead);
}
void RenderQuote::rendererSubtreeAttached(RenderObject* renderer)
{
if (renderer->documentBeingDestroyed())
return;
for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer))
if (descendant->isQuote()) {
toRenderQuote(descendant)->placeQuote();
break;
}
}
void RenderQuote::rendererRemovedFromTree(RenderObject* subtreeRoot)
{
if (subtreeRoot->documentBeingDestroyed())
return;
for (RenderObject* descendant = subtreeRoot; descendant; descendant = descendant->nextInPreOrder(subtreeRoot))
if (descendant->isQuote()) {
RenderQuote* removedQuote = toRenderQuote(descendant);
RenderQuote* lastQuoteBefore = removedQuote->m_previous;
removedQuote->m_previous = 0;
int depth = removedQuote->m_depth;
for (descendant = descendant->nextInPreOrder(subtreeRoot); descendant; descendant = descendant->nextInPreOrder(subtreeRoot))
if (descendant->isQuote())
removedQuote = toRenderQuote(descendant);
RenderQuote* quoteAfter = removedQuote->m_next;
removedQuote->m_next = 0;
if (lastQuoteBefore)
lastQuoteBefore->m_next = quoteAfter;
if (quoteAfter) {
quoteAfter->m_previous = lastQuoteBefore;
do {
if (depth == quoteAfter->m_depth)
break;
quoteAfter->m_depth = depth;
quoteAfter->setNeedsLayoutAndPrefWidthsRecalc();
adjustDepth(depth, quoteAfter->m_type);
quoteAfter = quoteAfter->m_next;
} while (quoteAfter);
}
break;
}
}
void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
const QuotesData* newQuotes = style()->quotes();
const QuotesData* oldQuotes = oldStyle ? oldStyle->quotes() : 0;
if (!((newQuotes && oldQuotes && (*newQuotes == *oldQuotes)) || (!newQuotes && !oldQuotes)))
setNeedsLayoutAndPrefWidthsRecalc();
RenderText::styleDidChange(diff, oldStyle);
}
} // namespace WebCore