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