1/**
2 * Copyright (C) 2011 Nokia Inc.  All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 */
20
21#include "config.h"
22#include "RenderQuote.h"
23
24#include "Document.h"
25#include "Element.h"
26#include "HTMLElement.h"
27#include "QuotesData.h"
28#include "RenderStyle.h"
29#include <algorithm>
30#include <wtf/text/AtomicString.h>
31#include <wtf/text/CString.h>
32
33#define UNKNOWN_DEPTH -1
34
35namespace WebCore {
36static inline void adjustDepth(int &depth, QuoteType type)
37{
38    switch (type) {
39    case OPEN_QUOTE:
40    case NO_OPEN_QUOTE:
41        ++depth;
42        break;
43    case CLOSE_QUOTE:
44    case NO_CLOSE_QUOTE:
45        if (depth)
46            --depth;
47        break;
48    default:
49        ASSERT_NOT_REACHED();
50    }
51}
52
53RenderQuote::RenderQuote(Document* node, QuoteType quote)
54    : RenderText(node, StringImpl::empty())
55    , m_type(quote)
56    , m_depth(UNKNOWN_DEPTH)
57    , m_next(0)
58    , m_previous(0)
59{
60}
61
62RenderQuote::~RenderQuote()
63{
64}
65
66const char* RenderQuote::renderName() const
67{
68    return "RenderQuote";
69}
70
71// This function places a list of quote renderers starting at "this" in the list of quote renderers already
72// in the document's renderer tree.
73// The assumptions are made (for performance):
74// 1. The list of quotes already in the renderers tree of the document is already in a consistent state
75// (All quote renderers are linked and have the correct depth set)
76// 2. The quote renderers of the inserted list are in a tree of renderers of their own which has been just
77// inserted in the main renderer tree with its root as child of some renderer.
78// 3. The quote renderers in the inserted list have depths consistent with their position in the list relative
79// to "this", thus if "this" does not need to change its depth upon insertion, the other renderers in the list don't
80// need to either.
81void RenderQuote::placeQuote()
82{
83    RenderQuote* head = this;
84    ASSERT(!head->m_previous);
85    RenderQuote* tail = 0;
86    for (RenderObject* predecessor = head->previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) {
87        if (!predecessor->isQuote())
88            continue;
89        head->m_previous = toRenderQuote(predecessor);
90        if (head->m_previous->m_next) {
91            // We need to splice the list of quotes headed by head into the document's list of quotes.
92            tail = head;
93            while (tail->m_next)
94                 tail = tail->m_next;
95            tail->m_next = head->m_previous->m_next;
96            ASSERT(tail->m_next->m_previous == head->m_previous);
97            tail->m_next->m_previous =  tail;
98            tail = tail->m_next; // This marks the splicing point here there may be a depth discontinuity
99        }
100        head->m_previous->m_next = head;
101        ASSERT(head->m_previous->m_depth != UNKNOWN_DEPTH);
102        break;
103    }
104    int newDepth;
105    if (!head->m_previous) {
106        newDepth = 0;
107        goto skipNewDepthCalc;
108    }
109    newDepth = head->m_previous->m_depth;
110    do {
111        adjustDepth(newDepth, head->m_previous->m_type);
112skipNewDepthCalc:
113        if (head->m_depth == newDepth) { // All remaining depth should be correct except if splicing was done.
114            if (!tail) // We've done the post splicing section already or there was no splicing.
115                break;
116            head = tail; // Continue after the splicing point
117            tail = 0; // Mark the possible splicing point discontinuity fixed.
118            newDepth = head->m_previous->m_depth;
119            continue;
120        }
121        head->m_depth = newDepth;
122        // FIXME: If the width and height of the quotation characters does not change we may only need to
123        // Invalidate the renderer's area not a relayout.
124        head->setNeedsLayoutAndPrefWidthsRecalc();
125        head = head->m_next;
126        if (head == tail) // We are at the splicing point
127            tail = 0; // Mark the possible depth discontinuity fixed.
128    } while (head);
129}
130
131#define ARRAY_SIZE(Carray) (sizeof(Carray) / sizeof(*Carray))
132#define LANGUAGE_DATA(name, languageSourceArray) { name, languageSourceArray, ARRAY_SIZE(languageSourceArray) }
133#define U(x) ((const UChar*)L##x)
134
135static const UChar* simpleQuotes[] = {U("\""), U("\""), U("'"), U("'")};
136
137static const UChar* englishQuotes[] = {U("\x201C"), U("\x201D"), U("\x2018"), U("\x2019")};
138static const UChar* norwegianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x2039"), U("\x203A") };
139static const UChar* romanianQuotes[] = { U("\x201E"), U("\x201D")};
140static const UChar* russianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x201E"), U("\x201C") };
141#undef U
142
143struct LanguageData {
144    const char *name;
145    const UChar* const* const array;
146    const int arraySize;
147    bool operator<(const LanguageData& compareTo) const
148    {
149        return strcmp(name, compareTo.name);
150    }
151};
152
153// Data mast be alphabetically sorted and in all lower case for fast comparison
154LanguageData languageData[] = {
155    LANGUAGE_DATA("en", englishQuotes),
156    LANGUAGE_DATA("no", norwegianQuotes),
157    LANGUAGE_DATA("ro", romanianQuotes),
158    LANGUAGE_DATA("ru", russianQuotes)
159};
160#undef LANGUAGE_DATA
161const LanguageData* const languageDataEnd = languageData + ARRAY_SIZE(languageData);
162
163#define defaultLanguageQuotesSource simpleQuotes
164#define defaultLanguageQuotesCount ARRAY_SIZE(defaultLanguageQuotesSource)
165
166static QuotesData* defaultLanguageQuotesValue = 0;
167static const QuotesData* defaultLanguageQuotes()
168{
169    if (!defaultLanguageQuotesValue) {
170        defaultLanguageQuotesValue = QuotesData::create(defaultLanguageQuotesCount);
171        if (!defaultLanguageQuotesValue)
172            return 0;
173        String* data = defaultLanguageQuotesValue->data();
174        for (size_t i = 0; i < defaultLanguageQuotesCount; ++i)
175            data[i] = defaultLanguageQuotesSource[i];
176    }
177    return defaultLanguageQuotesValue;
178}
179#undef defaultLanguageQuotesSource
180#undef defaultLanguageQuotesCount
181
182typedef HashMap<RefPtr<AtomicStringImpl>, QuotesData* > QuotesMap;
183
184static QuotesMap& quotesMap()
185{
186    DEFINE_STATIC_LOCAL(QuotesMap, staticQuotesMap, ());
187    return staticQuotesMap;
188}
189
190static const QuotesData* quotesForLanguage(AtomicStringImpl* language)
191{
192    QuotesData* returnValue;
193    AtomicString lower(language->lower());
194    returnValue = quotesMap().get(lower.impl());
195    if (returnValue)
196        return returnValue;
197    CString s(static_cast<const String&>(lower).ascii());
198    LanguageData request = { s.buffer()->data(), 0, 0 };
199    const LanguageData* lowerBound = std::lower_bound<const LanguageData*, const LanguageData>(languageData, languageDataEnd, request);
200    if (lowerBound == languageDataEnd)
201        return defaultLanguageQuotes();
202    if (strncmp(lowerBound->name, request.name, strlen(lowerBound->name)))
203        return defaultLanguageQuotes();
204    returnValue = QuotesData::create(lowerBound->arraySize);
205    if (!returnValue)
206        return defaultLanguageQuotes();
207    String* data = returnValue->data();
208    for (int i = 0; i < lowerBound->arraySize; ++i)
209        data[i] = lowerBound->array[i];
210    quotesMap().set(lower.impl(), returnValue);
211    return returnValue;
212}
213#undef ARRAY_SIZE
214
215static const QuotesData* defaultQuotes(const RenderObject* object)
216{
217    DEFINE_STATIC_LOCAL(String, langString, ("lang"));
218    Node* node =  object->generatingNode();
219    Element* element;
220    if (!node) {
221        element = object->document()->body();
222        if (!element)
223            element = object->document()->documentElement();
224    } else if (!node->isElementNode()) {
225        element = node->parentElement();
226        if (!element)
227            return defaultLanguageQuotes();
228    } else
229      element = toElement(node);
230    const AtomicString* language;
231    while ((language = &element->getAttribute(langString)) && language->isNull()) {
232        element = element->parentElement();
233        if (!element)
234            return defaultLanguageQuotes();
235    }
236    return quotesForLanguage(language->impl());
237}
238
239PassRefPtr<StringImpl> RenderQuote::originalText() const
240{
241    if (!parent())
242        return 0;
243    ASSERT(m_depth != UNKNOWN_DEPTH);
244    const QuotesData* quotes = style()->quotes();
245    if (!quotes)
246        quotes = defaultQuotes(this);
247    if (!quotes->length)
248        return emptyAtom.impl();
249    int index = m_depth * 2;
250    switch (m_type) {
251    case NO_OPEN_QUOTE:
252    case NO_CLOSE_QUOTE:
253        return String("").impl();
254    case CLOSE_QUOTE:
255        if (index)
256            --index;
257        else
258            ++index;
259        break;
260    case OPEN_QUOTE:
261        break;
262    default:
263        ASSERT_NOT_REACHED();
264        return emptyAtom.impl();
265    }
266    if (index >= quotes->length)
267        index = (quotes->length-2) | (index & 1);
268    if (index < 0)
269        return emptyAtom.impl();
270    return quotes->data()[index].impl();
271}
272
273void RenderQuote::computePreferredLogicalWidths(float lead)
274{
275    setTextInternal(originalText());
276    RenderText::computePreferredLogicalWidths(lead);
277}
278
279void RenderQuote::rendererSubtreeAttached(RenderObject* renderer)
280{
281    if (renderer->documentBeingDestroyed())
282        return;
283    for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer))
284        if (descendant->isQuote()) {
285            toRenderQuote(descendant)->placeQuote();
286            break;
287        }
288}
289
290void RenderQuote::rendererRemovedFromTree(RenderObject* subtreeRoot)
291{
292    if (subtreeRoot->documentBeingDestroyed())
293        return;
294    for (RenderObject* descendant = subtreeRoot; descendant; descendant = descendant->nextInPreOrder(subtreeRoot))
295        if (descendant->isQuote()) {
296            RenderQuote* removedQuote = toRenderQuote(descendant);
297            RenderQuote* lastQuoteBefore = removedQuote->m_previous;
298            removedQuote->m_previous = 0;
299            int depth = removedQuote->m_depth;
300            for (descendant = descendant->nextInPreOrder(subtreeRoot); descendant; descendant = descendant->nextInPreOrder(subtreeRoot))
301                if (descendant->isQuote())
302                    removedQuote = toRenderQuote(descendant);
303            RenderQuote* quoteAfter = removedQuote->m_next;
304            removedQuote->m_next = 0;
305            if (lastQuoteBefore)
306                lastQuoteBefore->m_next = quoteAfter;
307            if (quoteAfter) {
308                quoteAfter->m_previous = lastQuoteBefore;
309                do {
310                    if (depth == quoteAfter->m_depth)
311                        break;
312                    quoteAfter->m_depth = depth;
313                    quoteAfter->setNeedsLayoutAndPrefWidthsRecalc();
314                    adjustDepth(depth, quoteAfter->m_type);
315                    quoteAfter = quoteAfter->m_next;
316                } while (quoteAfter);
317            }
318            break;
319        }
320}
321
322void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
323{
324    const QuotesData* newQuotes = style()->quotes();
325    const QuotesData* oldQuotes = oldStyle ? oldStyle->quotes() : 0;
326    if (!((newQuotes && oldQuotes && (*newQuotes == *oldQuotes)) || (!newQuotes && !oldQuotes)))
327        setNeedsLayoutAndPrefWidthsRecalc();
328    RenderText::styleDidChange(diff, oldStyle);
329}
330
331} // namespace WebCore
332