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 35 namespace WebCore { 36 static 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 53 RenderQuote::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 62 RenderQuote::~RenderQuote() 63 { 64 } 65 66 const 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. 81 void 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); 112 skipNewDepthCalc: 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 135 static const UChar* simpleQuotes[] = {U("\""), U("\""), U("'"), U("'")}; 136 137 static const UChar* englishQuotes[] = {U("\x201C"), U("\x201D"), U("\x2018"), U("\x2019")}; 138 static const UChar* norwegianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x2039"), U("\x203A") }; 139 static const UChar* romanianQuotes[] = { U("\x201E"), U("\x201D")}; 140 static const UChar* russianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x201E"), U("\x201C") }; 141 #undef U 142 143 struct 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 154 LanguageData 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 161 const LanguageData* const languageDataEnd = languageData + ARRAY_SIZE(languageData); 162 163 #define defaultLanguageQuotesSource simpleQuotes 164 #define defaultLanguageQuotesCount ARRAY_SIZE(defaultLanguageQuotesSource) 165 166 static QuotesData* defaultLanguageQuotesValue = 0; 167 static 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 182 typedef HashMap<RefPtr<AtomicStringImpl>, QuotesData* > QuotesMap; 183 184 static QuotesMap& quotesMap() 185 { 186 DEFINE_STATIC_LOCAL(QuotesMap, staticQuotesMap, ()); 187 return staticQuotesMap; 188 } 189 190 static 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 215 static 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 239 PassRefPtr<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 273 void RenderQuote::computePreferredLogicalWidths(float lead) 274 { 275 setTextInternal(originalText()); 276 RenderText::computePreferredLogicalWidths(lead); 277 } 278 279 void 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 290 void 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 322 void 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