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