blob: d6e308e9c544357573f355520ebd4cd2ee05166b [file] [log] [blame]
/**
* Copyright (C) 2011 Nokia Inc. All rights reserved.
* Copyright (C) 2012 Google 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 "core/rendering/RenderQuote.h"
#include "core/rendering/RenderTextFragment.h"
#include "core/rendering/RenderView.h"
#include "wtf/StdLibExtras.h"
#include "wtf/text/AtomicString.h"
#include <algorithm>
namespace WebCore {
RenderQuote::RenderQuote(Document* node, QuoteType quote)
: RenderInline(0)
, m_type(quote)
, m_depth(0)
, m_next(0)
, m_previous(0)
, m_attached(false)
{
setDocumentForAnonymous(node);
}
RenderQuote::~RenderQuote()
{
ASSERT(!m_attached);
ASSERT(!m_next && !m_previous);
}
void RenderQuote::willBeDestroyed()
{
detachQuote();
RenderInline::willBeDestroyed();
}
void RenderQuote::willBeRemovedFromTree()
{
RenderInline::willBeRemovedFromTree();
detachQuote();
}
void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderInline::styleDidChange(diff, oldStyle);
updateText();
}
struct Language {
const char* lang;
UChar open1;
UChar close1;
UChar open2;
UChar close2;
QuotesData* data;
bool operator<(const Language& b) const { return strcmp(lang, b.lang) < 0; }
};
// Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quote
Language languages[] = {
{ "af", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "agq", 0x201e, 0x201d, 0x201a, 0x2019, 0 },
{ "ak", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "am", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "ar", 0x201d, 0x201c, 0x2019, 0x2018, 0 },
{ "asa", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "az-cyrl", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "bas", 0x00ab, 0x00bb, 0x201e, 0x201c, 0 },
{ "bem", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "bez", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "bg", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "bm", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "bn", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "br", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "brx", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "bs-cyrl" , 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "ca", 0x201c, 0x201d, 0x00ab, 0x00bb, 0 },
{ "cgg", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "chr", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "cs", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "da", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "dav", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "de", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "de-ch", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "dje", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "dua", 0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
{ "dyo", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "dz", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ebu", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ee", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "el", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "en", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "en-gb", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "es", 0x201c, 0x201d, 0x00ab, 0x00bb, 0 },
{ "et", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "eu", 0x201c, 0x201d, 0x00ab, 0x00bb, 0 },
{ "ewo", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "fa", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "ff", 0x201e, 0x201d, 0x201a, 0x2019, 0 },
{ "fi", 0x201d, 0x201d, 0x2019, 0x2019, 0 },
{ "fr", 0x00ab, 0x00bb, 0x00ab, 0x00bb, 0 },
{ "fr-ca", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "fr-ch", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "gsw", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "gu", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "guz", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ha", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "he", 0x0022, 0x0022, 0x0027, 0x0027, 0 },
{ "hi", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "hr", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "hu", 0x201e, 0x201d, 0x00bb, 0x00ab, 0 },
{ "id", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ig", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "it", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "ja", 0x300c, 0x300d, 0x300e, 0x300f, 0 },
{ "jgo", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "jmc", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "kab", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "kam", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "kde", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "kea", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "khq", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ki", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "kkj", 0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
{ "kln", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "km", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "kn", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ko", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ksb", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ksf", 0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
{ "lag", 0x201d, 0x201d, 0x2019, 0x2019, 0 },
{ "lg", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ln", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "lo", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "lt", 0x201e, 0x201c, 0x201e, 0x201c, 0 },
{ "lu", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "luo", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "luy", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "lv", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mas", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mer", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mfe", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mg", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "mgo", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mk", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "ml", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mr", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ms", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "mua", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "my", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "naq", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "nb", 0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
{ "nd", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "nl", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "nmg", 0x201e, 0x201d, 0x00ab, 0x00bb, 0 },
{ "nn", 0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
{ "nnh", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "nus", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "nyn", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "pl", 0x201e, 0x201d, 0x00ab, 0x00bb, 0 },
{ "pt", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "pt-pt", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "rn", 0x201d, 0x201d, 0x2019, 0x2019, 0 },
{ "ro", 0x201e, 0x201d, 0x00ab, 0x00bb, 0 },
{ "rof", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ru", 0x00ab, 0x00bb, 0x201e, 0x201c, 0 },
{ "rw", 0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
{ "rwk", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "saq", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "sbp", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "seh", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ses", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "sg", 0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
{ "shi", 0x00ab, 0x00bb, 0x201e, 0x201d, 0 },
{ "shi-tfng", 0x00ab, 0x00bb, 0x201e, 0x201d, 0 },
{ "si", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "sk", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "sl", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "sn", 0x201d, 0x201d, 0x2019, 0x2019, 0 },
{ "so", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "sq", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "sr", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "sr-latn", 0x201e, 0x201c, 0x201a, 0x2018, 0 },
{ "sv", 0x201d, 0x201d, 0x2019, 0x2019, 0 },
{ "sw", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "swc", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ta", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "te", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "teo", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "th", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "ti-er", 0x2018, 0x2019, 0x201c, 0x201d, 0 },
{ "to", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "tr", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "twq", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "tzm", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "uk", 0x00ab, 0x00bb, 0x201e, 0x201c, 0 },
{ "ur", 0x201d, 0x201c, 0x2019, 0x2018, 0 },
{ "vai", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "vai-latn", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "vi", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "vun", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "xh", 0x2018, 0x2019, 0x201c, 0x201d, 0 },
{ "xog", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "yav", 0x00ab, 0x00bb, 0x00ab, 0x00bb, 0 },
{ "yo", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "zh", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
{ "zh-hant", 0x300c, 0x300d, 0x300e, 0x300f, 0 },
{ "zu", 0x201c, 0x201d, 0x2018, 0x2019, 0 },
};
const QuotesData* quotesDataForLanguage(const AtomicString& lang)
{
if (lang.isNull())
return 0;
// This could be just a hash table, but doing that adds 200k to RenderQuote.o
Language* languagesEnd = languages + WTF_ARRAY_LENGTH(languages);
CString lowercaseLang = lang.string().lower().utf8();
Language key = { lowercaseLang.data(), 0, 0, 0, 0, 0 };
Language* match = std::lower_bound(languages, languagesEnd, key);
if (match == languagesEnd || strcmp(match->lang, key.lang))
return 0;
if (!match->data)
match->data = QuotesData::create(match->open1, match->close1, match->open2, match->close2).leakRef();
return match->data;
}
static const QuotesData* basicQuotesData()
{
// FIXME: The default quotes should be the fancy quotes for "en".
static QuotesData* staticBasicQuotes = QuotesData::create('"', '"', '\'', '\'').leakRef();
return staticBasicQuotes;
}
void RenderQuote::updateText()
{
String text = computeText();
if (m_text == text)
return;
m_text = text;
while (RenderObject* child = lastChild())
child->destroy();
RenderTextFragment* fragment = new RenderTextFragment(&document(), m_text.impl());
fragment->setStyle(style());
addChild(fragment);
}
String RenderQuote::computeText() const
{
switch (m_type) {
case NO_OPEN_QUOTE:
case NO_CLOSE_QUOTE:
return emptyString();
case CLOSE_QUOTE:
return quotesData()->getCloseQuote(m_depth - 1).impl();
case OPEN_QUOTE:
return quotesData()->getOpenQuote(m_depth).impl();
}
ASSERT_NOT_REACHED();
return emptyString();
}
const QuotesData* RenderQuote::quotesData() const
{
if (const QuotesData* customQuotes = style()->quotes())
return customQuotes;
if (const QuotesData* quotes = quotesDataForLanguage(style()->locale()))
return quotes;
return basicQuotesData();
}
void RenderQuote::attachQuote()
{
ASSERT(view());
ASSERT(!m_attached);
ASSERT(!m_next && !m_previous);
ASSERT(isRooted());
if (!view()->renderQuoteHead()) {
view()->setRenderQuoteHead(this);
m_attached = true;
return;
}
for (RenderObject* predecessor = previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) {
// Skip unattached predecessors to avoid having stale m_previous pointers
// if the previous node is never attached and is then destroyed.
if (!predecessor->isQuote() || !toRenderQuote(predecessor)->isAttached())
continue;
m_previous = toRenderQuote(predecessor);
m_next = m_previous->m_next;
m_previous->m_next = this;
if (m_next)
m_next->m_previous = this;
break;
}
if (!m_previous) {
m_next = view()->renderQuoteHead();
view()->setRenderQuoteHead(this);
if (m_next)
m_next->m_previous = this;
}
m_attached = true;
for (RenderQuote* quote = this; quote; quote = quote->m_next)
quote->updateDepth();
ASSERT(!m_next || m_next->m_attached);
ASSERT(!m_next || m_next->m_previous == this);
ASSERT(!m_previous || m_previous->m_attached);
ASSERT(!m_previous || m_previous->m_next == this);
}
void RenderQuote::detachQuote()
{
ASSERT(!m_next || m_next->m_attached);
ASSERT(!m_previous || m_previous->m_attached);
if (!m_attached)
return;
if (m_previous)
m_previous->m_next = m_next;
else if (view())
view()->setRenderQuoteHead(m_next);
if (m_next)
m_next->m_previous = m_previous;
if (!documentBeingDestroyed()) {
for (RenderQuote* quote = m_next; quote; quote = quote->m_next)
quote->updateDepth();
}
m_attached = false;
m_next = 0;
m_previous = 0;
m_depth = 0;
}
void RenderQuote::updateDepth()
{
ASSERT(m_attached);
int oldDepth = m_depth;
m_depth = 0;
if (m_previous) {
m_depth = m_previous->m_depth;
switch (m_previous->m_type) {
case OPEN_QUOTE:
case NO_OPEN_QUOTE:
m_depth++;
break;
case CLOSE_QUOTE:
case NO_CLOSE_QUOTE:
if (m_depth)
m_depth--;
break;
}
}
if (oldDepth != m_depth)
updateText();
}
} // namespace WebCore