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