1/**
2 * Copyright (C) 2011 Nokia Inc.  All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB.  If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22#include "config.h"
23#include "core/rendering/RenderQuote.h"
24
25#include "core/rendering/RenderTextFragment.h"
26#include "core/rendering/RenderView.h"
27#include "wtf/StdLibExtras.h"
28#include "wtf/text/AtomicString.h"
29
30#include <algorithm>
31
32namespace WebCore {
33
34RenderQuote::RenderQuote(Document* node, QuoteType quote)
35    : RenderInline(0)
36    , m_type(quote)
37    , m_depth(0)
38    , m_next(0)
39    , m_previous(0)
40    , m_attached(false)
41{
42    setDocumentForAnonymous(node);
43}
44
45RenderQuote::~RenderQuote()
46{
47    ASSERT(!m_attached);
48    ASSERT(!m_next && !m_previous);
49}
50
51void RenderQuote::willBeDestroyed()
52{
53    detachQuote();
54    RenderInline::willBeDestroyed();
55}
56
57void RenderQuote::willBeRemovedFromTree()
58{
59    RenderInline::willBeRemovedFromTree();
60    detachQuote();
61}
62
63void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
64{
65    RenderInline::styleDidChange(diff, oldStyle);
66    updateText();
67}
68
69struct Language {
70    const char* lang;
71    UChar open1;
72    UChar close1;
73    UChar open2;
74    UChar close2;
75    QuotesData* data;
76
77    bool operator<(const Language& b) const { return strcmp(lang, b.lang) < 0; }
78};
79
80// Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quote
81Language languages[] = {
82    { "af",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
83    { "agq",           0x201e, 0x201d, 0x201a, 0x2019, 0 },
84    { "ak",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
85    { "am",            0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
86    { "ar",            0x201d, 0x201c, 0x2019, 0x2018, 0 },
87    { "asa",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
88    { "az-cyrl",       0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
89    { "bas",           0x00ab, 0x00bb, 0x201e, 0x201c, 0 },
90    { "bem",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
91    { "bez",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
92    { "bg",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
93    { "bm",            0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
94    { "bn",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
95    { "br",            0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
96    { "brx",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
97    { "bs-cyrl" ,      0x201e, 0x201c, 0x201a, 0x2018, 0 },
98    { "ca",            0x201c, 0x201d, 0x00ab, 0x00bb, 0 },
99    { "cgg",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
100    { "chr",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
101    { "cs",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
102    { "da",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
103    { "dav",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
104    { "de",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
105    { "de-ch",         0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
106    { "dje",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
107    { "dua",           0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
108    { "dyo",           0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
109    { "dz",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
110    { "ebu",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
111    { "ee",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
112    { "el",            0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
113    { "en",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
114    { "en-gb",         0x201c, 0x201d, 0x2018, 0x2019, 0 },
115    { "es",            0x201c, 0x201d, 0x00ab, 0x00bb, 0 },
116    { "et",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
117    { "eu",            0x201c, 0x201d, 0x00ab, 0x00bb, 0 },
118    { "ewo",           0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
119    { "fa",            0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
120    { "ff",            0x201e, 0x201d, 0x201a, 0x2019, 0 },
121    { "fi",            0x201d, 0x201d, 0x2019, 0x2019, 0 },
122    { "fr",            0x00ab, 0x00bb, 0x00ab, 0x00bb, 0 },
123    { "fr-ca",         0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
124    { "fr-ch",         0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
125    { "gsw",           0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
126    { "gu",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
127    { "guz",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
128    { "ha",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
129    { "he",            0x0022, 0x0022, 0x0027, 0x0027, 0 },
130    { "hi",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
131    { "hr",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
132    { "hu",            0x201e, 0x201d, 0x00bb, 0x00ab, 0 },
133    { "id",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
134    { "ig",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
135    { "it",            0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
136    { "ja",            0x300c, 0x300d, 0x300e, 0x300f, 0 },
137    { "jgo",           0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
138    { "jmc",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
139    { "kab",           0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
140    { "kam",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
141    { "kde",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
142    { "kea",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
143    { "khq",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
144    { "ki",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
145    { "kkj",           0x00ab, 0x00bb, 0x2039, 0x203a, 0 },
146    { "kln",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
147    { "km",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
148    { "kn",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
149    { "ko",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
150    { "ksb",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
151    { "ksf",           0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
152    { "lag",           0x201d, 0x201d, 0x2019, 0x2019, 0 },
153    { "lg",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
154    { "ln",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
155    { "lo",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
156    { "lt",            0x201e, 0x201c, 0x201e, 0x201c, 0 },
157    { "lu",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
158    { "luo",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
159    { "luy",           0x201e, 0x201c, 0x201a, 0x2018, 0 },
160    { "lv",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
161    { "mas",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
162    { "mer",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
163    { "mfe",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
164    { "mg",            0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
165    { "mgo",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
166    { "mk",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
167    { "ml",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
168    { "mr",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
169    { "ms",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
170    { "mua",           0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
171    { "my",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
172    { "naq",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
173    { "nb",            0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
174    { "nd",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
175    { "nl",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
176    { "nmg",           0x201e, 0x201d, 0x00ab, 0x00bb, 0 },
177    { "nn",            0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
178    { "nnh",           0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
179    { "nus",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
180    { "nyn",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
181    { "pl",            0x201e, 0x201d, 0x00ab, 0x00bb, 0 },
182    { "pt",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
183    { "pt-pt",         0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
184    { "rn",            0x201d, 0x201d, 0x2019, 0x2019, 0 },
185    { "ro",            0x201e, 0x201d, 0x00ab, 0x00bb, 0 },
186    { "rof",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
187    { "ru",            0x00ab, 0x00bb, 0x201e, 0x201c, 0 },
188    { "rw",            0x00ab, 0x00bb, 0x2018, 0x2019, 0 },
189    { "rwk",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
190    { "saq",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
191    { "sbp",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
192    { "seh",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
193    { "ses",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
194    { "sg",            0x00ab, 0x00bb, 0x201c, 0x201d, 0 },
195    { "shi",           0x00ab, 0x00bb, 0x201e, 0x201d, 0 },
196    { "shi-tfng",      0x00ab, 0x00bb, 0x201e, 0x201d, 0 },
197    { "si",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
198    { "sk",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
199    { "sl",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
200    { "sn",            0x201d, 0x201d, 0x2019, 0x2019, 0 },
201    { "so",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
202    { "sq",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
203    { "sr",            0x201e, 0x201c, 0x201a, 0x2018, 0 },
204    { "sr-latn",       0x201e, 0x201c, 0x201a, 0x2018, 0 },
205    { "sv",            0x201d, 0x201d, 0x2019, 0x2019, 0 },
206    { "sw",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
207    { "swc",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
208    { "ta",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
209    { "te",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
210    { "teo",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
211    { "th",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
212    { "ti-er",         0x2018, 0x2019, 0x201c, 0x201d, 0 },
213    { "to",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
214    { "tr",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
215    { "twq",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
216    { "tzm",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
217    { "uk",            0x00ab, 0x00bb, 0x201e, 0x201c, 0 },
218    { "ur",            0x201d, 0x201c, 0x2019, 0x2018, 0 },
219    { "vai",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
220    { "vai-latn",      0x201c, 0x201d, 0x2018, 0x2019, 0 },
221    { "vi",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
222    { "vun",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
223    { "xh",            0x2018, 0x2019, 0x201c, 0x201d, 0 },
224    { "xog",           0x201c, 0x201d, 0x2018, 0x2019, 0 },
225    { "yav",           0x00ab, 0x00bb, 0x00ab, 0x00bb, 0 },
226    { "yo",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
227    { "zh",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
228    { "zh-hant",       0x300c, 0x300d, 0x300e, 0x300f, 0 },
229    { "zu",            0x201c, 0x201d, 0x2018, 0x2019, 0 },
230};
231
232const QuotesData* quotesDataForLanguage(const AtomicString& lang)
233{
234    if (lang.isNull())
235        return 0;
236
237    // This could be just a hash table, but doing that adds 200k to RenderQuote.o
238    Language* languagesEnd = languages + WTF_ARRAY_LENGTH(languages);
239    CString lowercaseLang = lang.string().lower().utf8();
240    Language key = { lowercaseLang.data(), 0, 0, 0, 0, 0 };
241    Language* match = std::lower_bound(languages, languagesEnd, key);
242    if (match == languagesEnd || strcmp(match->lang, key.lang))
243        return 0;
244
245    if (!match->data)
246        match->data = QuotesData::create(match->open1, match->close1, match->open2, match->close2).leakRef();
247
248    return match->data;
249}
250
251static const QuotesData* basicQuotesData()
252{
253    // FIXME: The default quotes should be the fancy quotes for "en".
254    static QuotesData* staticBasicQuotes = QuotesData::create('"', '"', '\'', '\'').leakRef();
255    return staticBasicQuotes;
256}
257
258void RenderQuote::updateText()
259{
260    String text = computeText();
261    if (m_text == text)
262        return;
263
264    m_text = text;
265
266    while (RenderObject* child = lastChild())
267        child->destroy();
268
269    RenderTextFragment* fragment = new RenderTextFragment(document(), m_text.impl());
270    fragment->setStyle(style());
271    addChild(fragment);
272}
273
274String RenderQuote::computeText() const
275{
276    switch (m_type) {
277    case NO_OPEN_QUOTE:
278    case NO_CLOSE_QUOTE:
279        return emptyString();
280    case CLOSE_QUOTE:
281        return quotesData()->getCloseQuote(m_depth - 1).impl();
282    case OPEN_QUOTE:
283        return quotesData()->getOpenQuote(m_depth).impl();
284    }
285    ASSERT_NOT_REACHED();
286    return emptyString();
287}
288
289const QuotesData* RenderQuote::quotesData() const
290{
291    if (const QuotesData* customQuotes = style()->quotes())
292        return customQuotes;
293
294    if (const QuotesData* quotes = quotesDataForLanguage(style()->locale()))
295        return quotes;
296
297    return basicQuotesData();
298}
299
300void RenderQuote::attachQuote()
301{
302    ASSERT(view());
303    ASSERT(!m_attached);
304    ASSERT(!m_next && !m_previous);
305    ASSERT(isRooted());
306
307    if (!view()->renderQuoteHead()) {
308        view()->setRenderQuoteHead(this);
309        m_attached = true;
310        return;
311    }
312
313    for (RenderObject* predecessor = previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) {
314        // Skip unattached predecessors to avoid having stale m_previous pointers
315        // if the previous node is never attached and is then destroyed.
316        if (!predecessor->isQuote() || !toRenderQuote(predecessor)->isAttached())
317            continue;
318        m_previous = toRenderQuote(predecessor);
319        m_next = m_previous->m_next;
320        m_previous->m_next = this;
321        if (m_next)
322            m_next->m_previous = this;
323        break;
324    }
325
326    if (!m_previous) {
327        m_next = view()->renderQuoteHead();
328        view()->setRenderQuoteHead(this);
329        if (m_next)
330            m_next->m_previous = this;
331    }
332    m_attached = true;
333
334    for (RenderQuote* quote = this; quote; quote = quote->m_next)
335        quote->updateDepth();
336
337    ASSERT(!m_next || m_next->m_attached);
338    ASSERT(!m_next || m_next->m_previous == this);
339    ASSERT(!m_previous || m_previous->m_attached);
340    ASSERT(!m_previous || m_previous->m_next == this);
341}
342
343void RenderQuote::detachQuote()
344{
345    ASSERT(!m_next || m_next->m_attached);
346    ASSERT(!m_previous || m_previous->m_attached);
347    if (!m_attached)
348        return;
349    if (m_previous)
350        m_previous->m_next = m_next;
351    else if (view())
352        view()->setRenderQuoteHead(m_next);
353    if (m_next)
354        m_next->m_previous = m_previous;
355    if (!documentBeingDestroyed()) {
356        for (RenderQuote* quote = m_next; quote; quote = quote->m_next)
357            quote->updateDepth();
358    }
359    m_attached = false;
360    m_next = 0;
361    m_previous = 0;
362    m_depth = 0;
363}
364
365void RenderQuote::updateDepth()
366{
367    ASSERT(m_attached);
368    int oldDepth = m_depth;
369    m_depth = 0;
370    if (m_previous) {
371        m_depth = m_previous->m_depth;
372        switch (m_previous->m_type) {
373        case OPEN_QUOTE:
374        case NO_OPEN_QUOTE:
375            m_depth++;
376            break;
377        case CLOSE_QUOTE:
378        case NO_CLOSE_QUOTE:
379            if (m_depth)
380                m_depth--;
381            break;
382        }
383    }
384    if (oldDepth != m_depth)
385        updateText();
386}
387
388} // namespace WebCore
389