1/*
2 * Copyright (C) 2011 Nokia Inc.  All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Copyright (C) 2013 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22
23#include "config.h"
24#include "RenderQuote.h"
25
26#include "QuotesData.h"
27#include "RenderTextFragment.h"
28#include "RenderView.h"
29
30using namespace WTF::Unicode;
31
32namespace WebCore {
33
34RenderQuote::RenderQuote(Document& document, PassRef<RenderStyle> style, QuoteType quote)
35    : RenderInline(document, WTF::move(style))
36    , m_type(quote)
37    , m_depth(-1)
38    , m_next(0)
39    , m_previous(0)
40    , m_isAttached(false)
41{
42}
43
44RenderQuote::~RenderQuote()
45{
46    ASSERT(!m_isAttached);
47    ASSERT(!m_next);
48    ASSERT(!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
69const unsigned maxDistinctQuoteCharacters = 16;
70
71#if !ASSERT_DISABLED
72
73static void checkNumberOfDistinctQuoteCharacters(UChar character)
74{
75    ASSERT(character);
76    static UChar distinctQuoteCharacters[maxDistinctQuoteCharacters];
77    for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
78        if (distinctQuoteCharacters[i] == character)
79            return;
80        if (!distinctQuoteCharacters[i]) {
81            distinctQuoteCharacters[i] = character;
82            return;
83        }
84    }
85    ASSERT_NOT_REACHED();
86}
87
88#endif
89
90struct QuotesForLanguage {
91    const char* language;
92    UChar open1;
93    UChar close1;
94    UChar open2;
95    UChar close2;
96};
97
98static int quoteTableLanguageComparisonFunction(const void* a, const void* b)
99{
100    return strcmp(static_cast<const QuotesForLanguage*>(a)->language,
101        static_cast<const QuotesForLanguage*>(b)->language);
102}
103
104static const QuotesForLanguage* quotesForLanguage(const String& language)
105{
106    // Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quotes
107    static const QuotesForLanguage quoteTable[] = {
108        { "af",         0x201c, 0x201d, 0x2018, 0x2019 },
109        { "agq",        0x201e, 0x201d, 0x201a, 0x2019 },
110        { "ak",         0x201c, 0x201d, 0x2018, 0x2019 },
111        { "am",         0x00ab, 0x00bb, 0x2039, 0x203a },
112        { "ar",         0x201d, 0x201c, 0x2019, 0x2018 },
113        { "asa",        0x201c, 0x201d, 0x2018, 0x2019 },
114        { "az-cyrl",    0x00ab, 0x00bb, 0x2039, 0x203a },
115        { "bas",        0x00ab, 0x00bb, 0x201e, 0x201c },
116        { "bem",        0x201c, 0x201d, 0x2018, 0x2019 },
117        { "bez",        0x201c, 0x201d, 0x2018, 0x2019 },
118        { "bg",         0x201e, 0x201c, 0x201a, 0x2018 },
119        { "bm",         0x00ab, 0x00bb, 0x201c, 0x201d },
120        { "bn",         0x201c, 0x201d, 0x2018, 0x2019 },
121        { "br",         0x00ab, 0x00bb, 0x2039, 0x203a },
122        { "brx",        0x201c, 0x201d, 0x2018, 0x2019 },
123        { "bs-cyrl",    0x201e, 0x201c, 0x201a, 0x2018 },
124        { "ca",         0x201c, 0x201d, 0x00ab, 0x00bb },
125        { "cgg",        0x201c, 0x201d, 0x2018, 0x2019 },
126        { "chr",        0x201c, 0x201d, 0x2018, 0x2019 },
127        { "cs",         0x201e, 0x201c, 0x201a, 0x2018 },
128        { "da",         0x201c, 0x201d, 0x2018, 0x2019 },
129        { "dav",        0x201c, 0x201d, 0x2018, 0x2019 },
130        { "de",         0x201e, 0x201c, 0x201a, 0x2018 },
131        { "de-ch",      0x00ab, 0x00bb, 0x2039, 0x203a },
132        { "dje",        0x201c, 0x201d, 0x2018, 0x2019 },
133        { "dua",        0x00ab, 0x00bb, 0x2018, 0x2019 },
134        { "dyo",        0x00ab, 0x00bb, 0x201c, 0x201d },
135        { "dz",         0x201c, 0x201d, 0x2018, 0x2019 },
136        { "ebu",        0x201c, 0x201d, 0x2018, 0x2019 },
137        { "ee",         0x201c, 0x201d, 0x2018, 0x2019 },
138        { "el",         0x00ab, 0x00bb, 0x201c, 0x201d },
139        { "en",         0x201c, 0x201d, 0x2018, 0x2019 },
140        { "en-gb",      0x201c, 0x201d, 0x2018, 0x2019 },
141        { "es",         0x201c, 0x201d, 0x00ab, 0x00bb },
142        { "et",         0x201e, 0x201c, 0x201a, 0x2018 },
143        { "eu",         0x201c, 0x201d, 0x00ab, 0x00bb },
144        { "ewo",        0x00ab, 0x00bb, 0x201c, 0x201d },
145        { "fa",         0x00ab, 0x00bb, 0x2039, 0x203a },
146        { "ff",         0x201e, 0x201d, 0x201a, 0x2019 },
147        { "fi",         0x201d, 0x201d, 0x2019, 0x2019 },
148        { "fr",         0x00ab, 0x00bb, 0x00ab, 0x00bb },
149        { "fr-ca",      0x00ab, 0x00bb, 0x2039, 0x203a },
150        { "fr-ch",      0x00ab, 0x00bb, 0x2039, 0x203a },
151        { "gsw",        0x00ab, 0x00bb, 0x2039, 0x203a },
152        { "gu",         0x201c, 0x201d, 0x2018, 0x2019 },
153        { "guz",        0x201c, 0x201d, 0x2018, 0x2019 },
154        { "ha",         0x201c, 0x201d, 0x2018, 0x2019 },
155        { "he",         0x0022, 0x0022, 0x0027, 0x0027 },
156        { "hi",         0x201c, 0x201d, 0x2018, 0x2019 },
157        { "hr",         0x201e, 0x201c, 0x201a, 0x2018 },
158        { "hu",         0x201e, 0x201d, 0x00bb, 0x00ab },
159        { "id",         0x201c, 0x201d, 0x2018, 0x2019 },
160        { "ig",         0x201c, 0x201d, 0x2018, 0x2019 },
161        { "it",         0x00ab, 0x00bb, 0x201c, 0x201d },
162        { "ja",         0x300c, 0x300d, 0x300e, 0x300f },
163        { "jgo",        0x00ab, 0x00bb, 0x2039, 0x203a },
164        { "jmc",        0x201c, 0x201d, 0x2018, 0x2019 },
165        { "kab",        0x00ab, 0x00bb, 0x201c, 0x201d },
166        { "kam",        0x201c, 0x201d, 0x2018, 0x2019 },
167        { "kde",        0x201c, 0x201d, 0x2018, 0x2019 },
168        { "kea",        0x201c, 0x201d, 0x2018, 0x2019 },
169        { "khq",        0x201c, 0x201d, 0x2018, 0x2019 },
170        { "ki",         0x201c, 0x201d, 0x2018, 0x2019 },
171        { "kkj",        0x00ab, 0x00bb, 0x2039, 0x203a },
172        { "kln",        0x201c, 0x201d, 0x2018, 0x2019 },
173        { "km",         0x201c, 0x201d, 0x2018, 0x2019 },
174        { "kn",         0x201c, 0x201d, 0x2018, 0x2019 },
175        { "ko",         0x201c, 0x201d, 0x2018, 0x2019 },
176        { "ksb",        0x201c, 0x201d, 0x2018, 0x2019 },
177        { "ksf",        0x00ab, 0x00bb, 0x2018, 0x2019 },
178        { "lag",        0x201d, 0x201d, 0x2019, 0x2019 },
179        { "lg",         0x201c, 0x201d, 0x2018, 0x2019 },
180        { "ln",         0x201c, 0x201d, 0x2018, 0x2019 },
181        { "lo",         0x201c, 0x201d, 0x2018, 0x2019 },
182        { "lt",         0x201e, 0x201c, 0x201e, 0x201c },
183        { "lu",         0x201c, 0x201d, 0x2018, 0x2019 },
184        { "luo",        0x201c, 0x201d, 0x2018, 0x2019 },
185        { "luy",        0x201e, 0x201c, 0x201a, 0x2018 },
186        { "lv",         0x201c, 0x201d, 0x2018, 0x2019 },
187        { "mas",        0x201c, 0x201d, 0x2018, 0x2019 },
188        { "mer",        0x201c, 0x201d, 0x2018, 0x2019 },
189        { "mfe",        0x201c, 0x201d, 0x2018, 0x2019 },
190        { "mg",         0x00ab, 0x00bb, 0x201c, 0x201d },
191        { "mgo",        0x201c, 0x201d, 0x2018, 0x2019 },
192        { "mk",         0x201e, 0x201c, 0x201a, 0x2018 },
193        { "ml",         0x201c, 0x201d, 0x2018, 0x2019 },
194        { "mr",         0x201c, 0x201d, 0x2018, 0x2019 },
195        { "ms",         0x201c, 0x201d, 0x2018, 0x2019 },
196        { "mua",        0x00ab, 0x00bb, 0x201c, 0x201d },
197        { "my",         0x201c, 0x201d, 0x2018, 0x2019 },
198        { "naq",        0x201c, 0x201d, 0x2018, 0x2019 },
199        { "nb",         0x00ab, 0x00bb, 0x2018, 0x2019 },
200        { "nd",         0x201c, 0x201d, 0x2018, 0x2019 },
201        { "nl",         0x201c, 0x201d, 0x2018, 0x2019 },
202        { "nmg",        0x201e, 0x201d, 0x00ab, 0x00bb },
203        { "nn",         0x00ab, 0x00bb, 0x2018, 0x2019 },
204        { "nnh",        0x00ab, 0x00bb, 0x201c, 0x201d },
205        { "nus",        0x201c, 0x201d, 0x2018, 0x2019 },
206        { "nyn",        0x201c, 0x201d, 0x2018, 0x2019 },
207        { "pl",         0x201e, 0x201d, 0x00ab, 0x00bb },
208        { "pt",         0x201c, 0x201d, 0x2018, 0x2019 },
209        { "pt-pt",      0x00ab, 0x00bb, 0x201c, 0x201d },
210        { "rn",         0x201d, 0x201d, 0x2019, 0x2019 },
211        { "ro",         0x201e, 0x201d, 0x00ab, 0x00bb },
212        { "rof",        0x201c, 0x201d, 0x2018, 0x2019 },
213        { "ru",         0x00ab, 0x00bb, 0x201e, 0x201c },
214        { "rw",         0x00ab, 0x00bb, 0x2018, 0x2019 },
215        { "rwk",        0x201c, 0x201d, 0x2018, 0x2019 },
216        { "saq",        0x201c, 0x201d, 0x2018, 0x2019 },
217        { "sbp",        0x201c, 0x201d, 0x2018, 0x2019 },
218        { "seh",        0x201c, 0x201d, 0x2018, 0x2019 },
219        { "ses",        0x201c, 0x201d, 0x2018, 0x2019 },
220        { "sg",         0x00ab, 0x00bb, 0x201c, 0x201d },
221        { "shi",        0x00ab, 0x00bb, 0x201e, 0x201d },
222        { "shi-tfng",   0x00ab, 0x00bb, 0x201e, 0x201d },
223        { "si",         0x201c, 0x201d, 0x2018, 0x2019 },
224        { "sk",         0x201e, 0x201c, 0x201a, 0x2018 },
225        { "sl",         0x201e, 0x201c, 0x201a, 0x2018 },
226        { "sn",         0x201d, 0x201d, 0x2019, 0x2019 },
227        { "so",         0x201c, 0x201d, 0x2018, 0x2019 },
228        { "sq",         0x201e, 0x201c, 0x201a, 0x2018 },
229        { "sr",         0x201e, 0x201c, 0x201a, 0x2018 },
230        { "sr-latn",    0x201e, 0x201c, 0x201a, 0x2018 },
231        { "sv",         0x201d, 0x201d, 0x2019, 0x2019 },
232        { "sw",         0x201c, 0x201d, 0x2018, 0x2019 },
233        { "swc",        0x201c, 0x201d, 0x2018, 0x2019 },
234        { "ta",         0x201c, 0x201d, 0x2018, 0x2019 },
235        { "te",         0x201c, 0x201d, 0x2018, 0x2019 },
236        { "teo",        0x201c, 0x201d, 0x2018, 0x2019 },
237        { "th",         0x201c, 0x201d, 0x2018, 0x2019 },
238        { "ti-er",      0x2018, 0x2019, 0x201c, 0x201d },
239        { "to",         0x201c, 0x201d, 0x2018, 0x2019 },
240        { "tr",         0x201c, 0x201d, 0x2018, 0x2019 },
241        { "twq",        0x201c, 0x201d, 0x2018, 0x2019 },
242        { "tzm",        0x201c, 0x201d, 0x2018, 0x2019 },
243        { "uk",         0x00ab, 0x00bb, 0x201e, 0x201c },
244        { "ur",         0x201d, 0x201c, 0x2019, 0x2018 },
245        { "vai",        0x201c, 0x201d, 0x2018, 0x2019 },
246        { "vai-latn",   0x201c, 0x201d, 0x2018, 0x2019 },
247        { "vi",         0x201c, 0x201d, 0x2018, 0x2019 },
248        { "vun",        0x201c, 0x201d, 0x2018, 0x2019 },
249        { "xh",         0x2018, 0x2019, 0x201c, 0x201d },
250        { "xog",        0x201c, 0x201d, 0x2018, 0x2019 },
251        { "yav",        0x00ab, 0x00bb, 0x00ab, 0x00bb },
252        { "yo",         0x201c, 0x201d, 0x2018, 0x2019 },
253        { "zh",         0x201c, 0x201d, 0x2018, 0x2019 },
254        { "zh-hant",    0x300c, 0x300d, 0x300e, 0x300f },
255        { "zu",         0x201c, 0x201d, 0x2018, 0x2019 },
256    };
257
258    const unsigned maxLanguageLength = 8;
259
260#if !ASSERT_DISABLED
261    // One time check that the table meets the constraints that the code below relies on.
262
263    static bool didOneTimeCheck = false;
264    if (!didOneTimeCheck) {
265        didOneTimeCheck = true;
266
267        checkNumberOfDistinctQuoteCharacters(quotationMark);
268        checkNumberOfDistinctQuoteCharacters(apostrophe);
269
270        for (unsigned i = 0; i < WTF_ARRAY_LENGTH(quoteTable); ++i) {
271            ASSERT(strlen(quoteTable[i].language) <= maxLanguageLength);
272
273            if (i)
274                ASSERT(strcmp(quoteTable[i - 1].language, quoteTable[i].language) < 0);
275
276            for (unsigned j = 0; UChar character = quoteTable[i].language[j]; ++j)
277                ASSERT(isASCIILower(character) || character == '-');
278
279            checkNumberOfDistinctQuoteCharacters(quoteTable[i].open1);
280            checkNumberOfDistinctQuoteCharacters(quoteTable[i].close1);
281            checkNumberOfDistinctQuoteCharacters(quoteTable[i].open2);
282            checkNumberOfDistinctQuoteCharacters(quoteTable[i].close2);
283        }
284    }
285#endif
286
287    unsigned length = language.length();
288    if (!length || length > maxLanguageLength)
289        return 0;
290
291    char languageKeyBuffer[maxLanguageLength + 1];
292    for (unsigned i = 0; i < length; ++i) {
293        UChar character = toASCIILower(language[i]);
294        if (!(isASCIILower(character) || character == '-'))
295            return 0;
296        languageKeyBuffer[i] = static_cast<char>(character);
297    }
298    languageKeyBuffer[length] = 0;
299
300    QuotesForLanguage languageKey = { languageKeyBuffer, 0, 0, 0, 0 };
301
302    return static_cast<const QuotesForLanguage*>(bsearch(&languageKey,
303        quoteTable, WTF_ARRAY_LENGTH(quoteTable), sizeof(quoteTable[0]), quoteTableLanguageComparisonFunction));
304}
305
306static StringImpl* stringForQuoteCharacter(UChar character)
307{
308    // Use linear search because there is a small number of distinct characters, thus binary search is unneeded.
309    ASSERT(character);
310    struct StringForCharacter {
311        UChar character;
312        StringImpl* string;
313    };
314    static StringForCharacter strings[maxDistinctQuoteCharacters];
315    for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
316        if (strings[i].character == character)
317            return strings[i].string;
318        if (!strings[i].character) {
319            strings[i].character = character;
320            strings[i].string = &StringImpl::create8BitIfPossible(&character, 1).leakRef();
321            return strings[i].string;
322        }
323    }
324    ASSERT_NOT_REACHED();
325    return StringImpl::empty();
326}
327
328static inline StringImpl* quotationMarkString()
329{
330    static StringImpl* quotationMarkString = stringForQuoteCharacter(quotationMark);
331    return quotationMarkString;
332}
333
334static inline StringImpl* apostropheString()
335{
336    static StringImpl* apostropheString = stringForQuoteCharacter(apostrophe);
337    return apostropheString;
338}
339
340void RenderQuote::updateText()
341{
342    String text = computeText();
343    if (m_text == text)
344        return;
345
346    while (RenderObject* child = lastChild())
347        child->destroy();
348
349    if (text == emptyString() || text == String()) {
350        m_text = String();
351        return;
352    }
353
354    m_text = text;
355
356    RenderTextFragment* fragment = new RenderTextFragment(document(), m_text.impl());
357    addChild(fragment);
358}
359
360String RenderQuote::computeText() const
361{
362    if (m_depth < 0)
363        return emptyString();
364    bool isOpenQuote = false;
365    switch (m_type) {
366    case NO_OPEN_QUOTE:
367    case NO_CLOSE_QUOTE:
368        return emptyString();
369    case OPEN_QUOTE:
370        isOpenQuote = true;
371        FALLTHROUGH;
372    case CLOSE_QUOTE:
373        if (const QuotesData* quotes = style().quotes())
374            return isOpenQuote ? quotes->openQuote(m_depth).impl() : quotes->closeQuote(m_depth).impl();
375        if (const QuotesForLanguage* quotes = quotesForLanguage(style().locale()))
376            return stringForQuoteCharacter(isOpenQuote ? (m_depth ? quotes->open2 : quotes->open1) : (m_depth ? quotes->close2 : quotes->close1));
377        // FIXME: Should the default be the quotes for "en" rather than straight quotes?
378        return m_depth ? apostropheString() : quotationMarkString();
379    }
380    ASSERT_NOT_REACHED();
381    return emptyString();
382}
383
384void RenderQuote::attachQuote()
385{
386    ASSERT(!m_isAttached);
387    ASSERT(!m_next);
388    ASSERT(!m_previous);
389    ASSERT(isRooted());
390
391    // Optimize case where this is the first quote in a RenderView by not searching for predecessors in that case.
392    if (view().renderQuoteHead()) {
393        for (RenderObject* predecessor = previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) {
394            // Skip unattached predecessors to avoid having stale m_previous pointers
395            // if the previous node is never attached and is then destroyed.
396            if (!predecessor->isQuote() || !toRenderQuote(predecessor)->m_isAttached)
397                continue;
398            m_previous = toRenderQuote(predecessor);
399            m_next = m_previous->m_next;
400            m_previous->m_next = this;
401            if (m_next)
402                m_next->m_previous = this;
403            break;
404        }
405    }
406
407    if (!m_previous) {
408        m_next = view().renderQuoteHead();
409        view().setRenderQuoteHead(this);
410        if (m_next)
411            m_next->m_previous = this;
412    }
413
414    m_isAttached = true;
415
416    for (RenderQuote* quote = this; quote; quote = quote->m_next)
417        quote->updateDepth();
418
419    ASSERT(!m_next || m_next->m_isAttached);
420    ASSERT(!m_next || m_next->m_previous == this);
421    ASSERT(!m_previous || m_previous->m_isAttached);
422    ASSERT(!m_previous || m_previous->m_next == this);
423}
424
425void RenderQuote::detachQuote()
426{
427    ASSERT(!m_next || m_next->m_isAttached);
428    ASSERT(!m_previous || m_previous->m_isAttached);
429    if (!m_isAttached)
430        return;
431    if (m_previous)
432        m_previous->m_next = m_next;
433    else
434        view().setRenderQuoteHead(m_next);
435    if (m_next)
436        m_next->m_previous = m_previous;
437    if (!documentBeingDestroyed()) {
438        for (RenderQuote* quote = m_next; quote; quote = quote->m_next)
439            quote->updateDepth();
440    }
441    m_isAttached = false;
442    m_next = 0;
443    m_previous = 0;
444}
445
446void RenderQuote::updateDepth()
447{
448    ASSERT(m_isAttached);
449    int depth = 0;
450    if (m_previous) {
451        depth = m_previous->m_depth;
452        if (depth < 0)
453            depth = 0;
454        switch (m_previous->m_type) {
455        case OPEN_QUOTE:
456        case NO_OPEN_QUOTE:
457            depth++;
458            break;
459        case CLOSE_QUOTE:
460        case NO_CLOSE_QUOTE:
461            break;
462        }
463    }
464    switch (m_type) {
465    case OPEN_QUOTE:
466    case NO_OPEN_QUOTE:
467        break;
468    case CLOSE_QUOTE:
469    case NO_CLOSE_QUOTE:
470        depth--;
471        break;
472    }
473    if (m_depth == depth)
474        return;
475    m_depth = depth;
476    updateText();
477}
478
479} // namespace WebCore
480