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