1/* 2 * Copyright (C) 2011 Nokia Inc. All rights reserved. 3 * Copyright (C) 2012 Google Inc. All rights reserved. 4 * Copyright (C) 2013 Apple Inc. All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 * 21 */ 22 23#include "config.h" 24#include "RenderQuote.h" 25 26#include "QuotesData.h" 27#include "RenderTextFragment.h" 28#include "RenderView.h" 29 30using namespace WTF::Unicode; 31 32namespace WebCore { 33 34RenderQuote::RenderQuote(Document& document, PassRef<RenderStyle> style, QuoteType quote) 35 : RenderInline(document, WTF::move(style)) 36 , m_type(quote) 37 , m_depth(-1) 38 , m_next(0) 39 , m_previous(0) 40 , m_isAttached(false) 41{ 42} 43 44RenderQuote::~RenderQuote() 45{ 46 ASSERT(!m_isAttached); 47 ASSERT(!m_next); 48 ASSERT(!m_previous); 49} 50 51void RenderQuote::willBeDestroyed() 52{ 53 detachQuote(); 54 RenderInline::willBeDestroyed(); 55} 56 57void RenderQuote::willBeRemovedFromTree() 58{ 59 RenderInline::willBeRemovedFromTree(); 60 detachQuote(); 61} 62 63void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 64{ 65 RenderInline::styleDidChange(diff, oldStyle); 66 updateText(); 67} 68 69const unsigned maxDistinctQuoteCharacters = 16; 70 71#if !ASSERT_DISABLED 72 73static void checkNumberOfDistinctQuoteCharacters(UChar character) 74{ 75 ASSERT(character); 76 static UChar distinctQuoteCharacters[maxDistinctQuoteCharacters]; 77 for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) { 78 if (distinctQuoteCharacters[i] == character) 79 return; 80 if (!distinctQuoteCharacters[i]) { 81 distinctQuoteCharacters[i] = character; 82 return; 83 } 84 } 85 ASSERT_NOT_REACHED(); 86} 87 88#endif 89 90struct QuotesForLanguage { 91 const char* language; 92 UChar open1; 93 UChar close1; 94 UChar open2; 95 UChar close2; 96}; 97 98static int quoteTableLanguageComparisonFunction(const void* a, const void* b) 99{ 100 return strcmp(static_cast<const QuotesForLanguage*>(a)->language, 101 static_cast<const QuotesForLanguage*>(b)->language); 102} 103 104static const QuotesForLanguage* quotesForLanguage(const String& language) 105{ 106 // Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quotes 107 static const QuotesForLanguage quoteTable[] = { 108 { "af", 0x201c, 0x201d, 0x2018, 0x2019 }, 109 { "agq", 0x201e, 0x201d, 0x201a, 0x2019 }, 110 { "ak", 0x201c, 0x201d, 0x2018, 0x2019 }, 111 { "am", 0x00ab, 0x00bb, 0x2039, 0x203a }, 112 { "ar", 0x201d, 0x201c, 0x2019, 0x2018 }, 113 { "asa", 0x201c, 0x201d, 0x2018, 0x2019 }, 114 { "az-cyrl", 0x00ab, 0x00bb, 0x2039, 0x203a }, 115 { "bas", 0x00ab, 0x00bb, 0x201e, 0x201c }, 116 { "bem", 0x201c, 0x201d, 0x2018, 0x2019 }, 117 { "bez", 0x201c, 0x201d, 0x2018, 0x2019 }, 118 { "bg", 0x201e, 0x201c, 0x201a, 0x2018 }, 119 { "bm", 0x00ab, 0x00bb, 0x201c, 0x201d }, 120 { "bn", 0x201c, 0x201d, 0x2018, 0x2019 }, 121 { "br", 0x00ab, 0x00bb, 0x2039, 0x203a }, 122 { "brx", 0x201c, 0x201d, 0x2018, 0x2019 }, 123 { "bs-cyrl", 0x201e, 0x201c, 0x201a, 0x2018 }, 124 { "ca", 0x201c, 0x201d, 0x00ab, 0x00bb }, 125 { "cgg", 0x201c, 0x201d, 0x2018, 0x2019 }, 126 { "chr", 0x201c, 0x201d, 0x2018, 0x2019 }, 127 { "cs", 0x201e, 0x201c, 0x201a, 0x2018 }, 128 { "da", 0x201c, 0x201d, 0x2018, 0x2019 }, 129 { "dav", 0x201c, 0x201d, 0x2018, 0x2019 }, 130 { "de", 0x201e, 0x201c, 0x201a, 0x2018 }, 131 { "de-ch", 0x00ab, 0x00bb, 0x2039, 0x203a }, 132 { "dje", 0x201c, 0x201d, 0x2018, 0x2019 }, 133 { "dua", 0x00ab, 0x00bb, 0x2018, 0x2019 }, 134 { "dyo", 0x00ab, 0x00bb, 0x201c, 0x201d }, 135 { "dz", 0x201c, 0x201d, 0x2018, 0x2019 }, 136 { "ebu", 0x201c, 0x201d, 0x2018, 0x2019 }, 137 { "ee", 0x201c, 0x201d, 0x2018, 0x2019 }, 138 { "el", 0x00ab, 0x00bb, 0x201c, 0x201d }, 139 { "en", 0x201c, 0x201d, 0x2018, 0x2019 }, 140 { "en-gb", 0x201c, 0x201d, 0x2018, 0x2019 }, 141 { "es", 0x201c, 0x201d, 0x00ab, 0x00bb }, 142 { "et", 0x201e, 0x201c, 0x201a, 0x2018 }, 143 { "eu", 0x201c, 0x201d, 0x00ab, 0x00bb }, 144 { "ewo", 0x00ab, 0x00bb, 0x201c, 0x201d }, 145 { "fa", 0x00ab, 0x00bb, 0x2039, 0x203a }, 146 { "ff", 0x201e, 0x201d, 0x201a, 0x2019 }, 147 { "fi", 0x201d, 0x201d, 0x2019, 0x2019 }, 148 { "fr", 0x00ab, 0x00bb, 0x00ab, 0x00bb }, 149 { "fr-ca", 0x00ab, 0x00bb, 0x2039, 0x203a }, 150 { "fr-ch", 0x00ab, 0x00bb, 0x2039, 0x203a }, 151 { "gsw", 0x00ab, 0x00bb, 0x2039, 0x203a }, 152 { "gu", 0x201c, 0x201d, 0x2018, 0x2019 }, 153 { "guz", 0x201c, 0x201d, 0x2018, 0x2019 }, 154 { "ha", 0x201c, 0x201d, 0x2018, 0x2019 }, 155 { "he", 0x0022, 0x0022, 0x0027, 0x0027 }, 156 { "hi", 0x201c, 0x201d, 0x2018, 0x2019 }, 157 { "hr", 0x201e, 0x201c, 0x201a, 0x2018 }, 158 { "hu", 0x201e, 0x201d, 0x00bb, 0x00ab }, 159 { "id", 0x201c, 0x201d, 0x2018, 0x2019 }, 160 { "ig", 0x201c, 0x201d, 0x2018, 0x2019 }, 161 { "it", 0x00ab, 0x00bb, 0x201c, 0x201d }, 162 { "ja", 0x300c, 0x300d, 0x300e, 0x300f }, 163 { "jgo", 0x00ab, 0x00bb, 0x2039, 0x203a }, 164 { "jmc", 0x201c, 0x201d, 0x2018, 0x2019 }, 165 { "kab", 0x00ab, 0x00bb, 0x201c, 0x201d }, 166 { "kam", 0x201c, 0x201d, 0x2018, 0x2019 }, 167 { "kde", 0x201c, 0x201d, 0x2018, 0x2019 }, 168 { "kea", 0x201c, 0x201d, 0x2018, 0x2019 }, 169 { "khq", 0x201c, 0x201d, 0x2018, 0x2019 }, 170 { "ki", 0x201c, 0x201d, 0x2018, 0x2019 }, 171 { "kkj", 0x00ab, 0x00bb, 0x2039, 0x203a }, 172 { "kln", 0x201c, 0x201d, 0x2018, 0x2019 }, 173 { "km", 0x201c, 0x201d, 0x2018, 0x2019 }, 174 { "kn", 0x201c, 0x201d, 0x2018, 0x2019 }, 175 { "ko", 0x201c, 0x201d, 0x2018, 0x2019 }, 176 { "ksb", 0x201c, 0x201d, 0x2018, 0x2019 }, 177 { "ksf", 0x00ab, 0x00bb, 0x2018, 0x2019 }, 178 { "lag", 0x201d, 0x201d, 0x2019, 0x2019 }, 179 { "lg", 0x201c, 0x201d, 0x2018, 0x2019 }, 180 { "ln", 0x201c, 0x201d, 0x2018, 0x2019 }, 181 { "lo", 0x201c, 0x201d, 0x2018, 0x2019 }, 182 { "lt", 0x201e, 0x201c, 0x201e, 0x201c }, 183 { "lu", 0x201c, 0x201d, 0x2018, 0x2019 }, 184 { "luo", 0x201c, 0x201d, 0x2018, 0x2019 }, 185 { "luy", 0x201e, 0x201c, 0x201a, 0x2018 }, 186 { "lv", 0x201c, 0x201d, 0x2018, 0x2019 }, 187 { "mas", 0x201c, 0x201d, 0x2018, 0x2019 }, 188 { "mer", 0x201c, 0x201d, 0x2018, 0x2019 }, 189 { "mfe", 0x201c, 0x201d, 0x2018, 0x2019 }, 190 { "mg", 0x00ab, 0x00bb, 0x201c, 0x201d }, 191 { "mgo", 0x201c, 0x201d, 0x2018, 0x2019 }, 192 { "mk", 0x201e, 0x201c, 0x201a, 0x2018 }, 193 { "ml", 0x201c, 0x201d, 0x2018, 0x2019 }, 194 { "mr", 0x201c, 0x201d, 0x2018, 0x2019 }, 195 { "ms", 0x201c, 0x201d, 0x2018, 0x2019 }, 196 { "mua", 0x00ab, 0x00bb, 0x201c, 0x201d }, 197 { "my", 0x201c, 0x201d, 0x2018, 0x2019 }, 198 { "naq", 0x201c, 0x201d, 0x2018, 0x2019 }, 199 { "nb", 0x00ab, 0x00bb, 0x2018, 0x2019 }, 200 { "nd", 0x201c, 0x201d, 0x2018, 0x2019 }, 201 { "nl", 0x201c, 0x201d, 0x2018, 0x2019 }, 202 { "nmg", 0x201e, 0x201d, 0x00ab, 0x00bb }, 203 { "nn", 0x00ab, 0x00bb, 0x2018, 0x2019 }, 204 { "nnh", 0x00ab, 0x00bb, 0x201c, 0x201d }, 205 { "nus", 0x201c, 0x201d, 0x2018, 0x2019 }, 206 { "nyn", 0x201c, 0x201d, 0x2018, 0x2019 }, 207 { "pl", 0x201e, 0x201d, 0x00ab, 0x00bb }, 208 { "pt", 0x201c, 0x201d, 0x2018, 0x2019 }, 209 { "pt-pt", 0x00ab, 0x00bb, 0x201c, 0x201d }, 210 { "rn", 0x201d, 0x201d, 0x2019, 0x2019 }, 211 { "ro", 0x201e, 0x201d, 0x00ab, 0x00bb }, 212 { "rof", 0x201c, 0x201d, 0x2018, 0x2019 }, 213 { "ru", 0x00ab, 0x00bb, 0x201e, 0x201c }, 214 { "rw", 0x00ab, 0x00bb, 0x2018, 0x2019 }, 215 { "rwk", 0x201c, 0x201d, 0x2018, 0x2019 }, 216 { "saq", 0x201c, 0x201d, 0x2018, 0x2019 }, 217 { "sbp", 0x201c, 0x201d, 0x2018, 0x2019 }, 218 { "seh", 0x201c, 0x201d, 0x2018, 0x2019 }, 219 { "ses", 0x201c, 0x201d, 0x2018, 0x2019 }, 220 { "sg", 0x00ab, 0x00bb, 0x201c, 0x201d }, 221 { "shi", 0x00ab, 0x00bb, 0x201e, 0x201d }, 222 { "shi-tfng", 0x00ab, 0x00bb, 0x201e, 0x201d }, 223 { "si", 0x201c, 0x201d, 0x2018, 0x2019 }, 224 { "sk", 0x201e, 0x201c, 0x201a, 0x2018 }, 225 { "sl", 0x201e, 0x201c, 0x201a, 0x2018 }, 226 { "sn", 0x201d, 0x201d, 0x2019, 0x2019 }, 227 { "so", 0x201c, 0x201d, 0x2018, 0x2019 }, 228 { "sq", 0x201e, 0x201c, 0x201a, 0x2018 }, 229 { "sr", 0x201e, 0x201c, 0x201a, 0x2018 }, 230 { "sr-latn", 0x201e, 0x201c, 0x201a, 0x2018 }, 231 { "sv", 0x201d, 0x201d, 0x2019, 0x2019 }, 232 { "sw", 0x201c, 0x201d, 0x2018, 0x2019 }, 233 { "swc", 0x201c, 0x201d, 0x2018, 0x2019 }, 234 { "ta", 0x201c, 0x201d, 0x2018, 0x2019 }, 235 { "te", 0x201c, 0x201d, 0x2018, 0x2019 }, 236 { "teo", 0x201c, 0x201d, 0x2018, 0x2019 }, 237 { "th", 0x201c, 0x201d, 0x2018, 0x2019 }, 238 { "ti-er", 0x2018, 0x2019, 0x201c, 0x201d }, 239 { "to", 0x201c, 0x201d, 0x2018, 0x2019 }, 240 { "tr", 0x201c, 0x201d, 0x2018, 0x2019 }, 241 { "twq", 0x201c, 0x201d, 0x2018, 0x2019 }, 242 { "tzm", 0x201c, 0x201d, 0x2018, 0x2019 }, 243 { "uk", 0x00ab, 0x00bb, 0x201e, 0x201c }, 244 { "ur", 0x201d, 0x201c, 0x2019, 0x2018 }, 245 { "vai", 0x201c, 0x201d, 0x2018, 0x2019 }, 246 { "vai-latn", 0x201c, 0x201d, 0x2018, 0x2019 }, 247 { "vi", 0x201c, 0x201d, 0x2018, 0x2019 }, 248 { "vun", 0x201c, 0x201d, 0x2018, 0x2019 }, 249 { "xh", 0x2018, 0x2019, 0x201c, 0x201d }, 250 { "xog", 0x201c, 0x201d, 0x2018, 0x2019 }, 251 { "yav", 0x00ab, 0x00bb, 0x00ab, 0x00bb }, 252 { "yo", 0x201c, 0x201d, 0x2018, 0x2019 }, 253 { "zh", 0x201c, 0x201d, 0x2018, 0x2019 }, 254 { "zh-hant", 0x300c, 0x300d, 0x300e, 0x300f }, 255 { "zu", 0x201c, 0x201d, 0x2018, 0x2019 }, 256 }; 257 258 const unsigned maxLanguageLength = 8; 259 260#if !ASSERT_DISABLED 261 // One time check that the table meets the constraints that the code below relies on. 262 263 static bool didOneTimeCheck = false; 264 if (!didOneTimeCheck) { 265 didOneTimeCheck = true; 266 267 checkNumberOfDistinctQuoteCharacters(quotationMark); 268 checkNumberOfDistinctQuoteCharacters(apostrophe); 269 270 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(quoteTable); ++i) { 271 ASSERT(strlen(quoteTable[i].language) <= maxLanguageLength); 272 273 if (i) 274 ASSERT(strcmp(quoteTable[i - 1].language, quoteTable[i].language) < 0); 275 276 for (unsigned j = 0; UChar character = quoteTable[i].language[j]; ++j) 277 ASSERT(isASCIILower(character) || character == '-'); 278 279 checkNumberOfDistinctQuoteCharacters(quoteTable[i].open1); 280 checkNumberOfDistinctQuoteCharacters(quoteTable[i].close1); 281 checkNumberOfDistinctQuoteCharacters(quoteTable[i].open2); 282 checkNumberOfDistinctQuoteCharacters(quoteTable[i].close2); 283 } 284 } 285#endif 286 287 unsigned length = language.length(); 288 if (!length || length > maxLanguageLength) 289 return 0; 290 291 char languageKeyBuffer[maxLanguageLength + 1]; 292 for (unsigned i = 0; i < length; ++i) { 293 UChar character = toASCIILower(language[i]); 294 if (!(isASCIILower(character) || character == '-')) 295 return 0; 296 languageKeyBuffer[i] = static_cast<char>(character); 297 } 298 languageKeyBuffer[length] = 0; 299 300 QuotesForLanguage languageKey = { languageKeyBuffer, 0, 0, 0, 0 }; 301 302 return static_cast<const QuotesForLanguage*>(bsearch(&languageKey, 303 quoteTable, WTF_ARRAY_LENGTH(quoteTable), sizeof(quoteTable[0]), quoteTableLanguageComparisonFunction)); 304} 305 306static StringImpl* stringForQuoteCharacter(UChar character) 307{ 308 // Use linear search because there is a small number of distinct characters, thus binary search is unneeded. 309 ASSERT(character); 310 struct StringForCharacter { 311 UChar character; 312 StringImpl* string; 313 }; 314 static StringForCharacter strings[maxDistinctQuoteCharacters]; 315 for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) { 316 if (strings[i].character == character) 317 return strings[i].string; 318 if (!strings[i].character) { 319 strings[i].character = character; 320 strings[i].string = &StringImpl::create8BitIfPossible(&character, 1).leakRef(); 321 return strings[i].string; 322 } 323 } 324 ASSERT_NOT_REACHED(); 325 return StringImpl::empty(); 326} 327 328static inline StringImpl* quotationMarkString() 329{ 330 static StringImpl* quotationMarkString = stringForQuoteCharacter(quotationMark); 331 return quotationMarkString; 332} 333 334static inline StringImpl* apostropheString() 335{ 336 static StringImpl* apostropheString = stringForQuoteCharacter(apostrophe); 337 return apostropheString; 338} 339 340void RenderQuote::updateText() 341{ 342 String text = computeText(); 343 if (m_text == text) 344 return; 345 346 while (RenderObject* child = lastChild()) 347 child->destroy(); 348 349 if (text == emptyString() || text == String()) { 350 m_text = String(); 351 return; 352 } 353 354 m_text = text; 355 356 RenderTextFragment* fragment = new RenderTextFragment(document(), m_text.impl()); 357 addChild(fragment); 358} 359 360String RenderQuote::computeText() const 361{ 362 if (m_depth < 0) 363 return emptyString(); 364 bool isOpenQuote = false; 365 switch (m_type) { 366 case NO_OPEN_QUOTE: 367 case NO_CLOSE_QUOTE: 368 return emptyString(); 369 case OPEN_QUOTE: 370 isOpenQuote = true; 371 FALLTHROUGH; 372 case CLOSE_QUOTE: 373 if (const QuotesData* quotes = style().quotes()) 374 return isOpenQuote ? quotes->openQuote(m_depth).impl() : quotes->closeQuote(m_depth).impl(); 375 if (const QuotesForLanguage* quotes = quotesForLanguage(style().locale())) 376 return stringForQuoteCharacter(isOpenQuote ? (m_depth ? quotes->open2 : quotes->open1) : (m_depth ? quotes->close2 : quotes->close1)); 377 // FIXME: Should the default be the quotes for "en" rather than straight quotes? 378 return m_depth ? apostropheString() : quotationMarkString(); 379 } 380 ASSERT_NOT_REACHED(); 381 return emptyString(); 382} 383 384void RenderQuote::attachQuote() 385{ 386 ASSERT(!m_isAttached); 387 ASSERT(!m_next); 388 ASSERT(!m_previous); 389 ASSERT(isRooted()); 390 391 // Optimize case where this is the first quote in a RenderView by not searching for predecessors in that case. 392 if (view().renderQuoteHead()) { 393 for (RenderObject* predecessor = previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) { 394 // Skip unattached predecessors to avoid having stale m_previous pointers 395 // if the previous node is never attached and is then destroyed. 396 if (!predecessor->isQuote() || !toRenderQuote(predecessor)->m_isAttached) 397 continue; 398 m_previous = toRenderQuote(predecessor); 399 m_next = m_previous->m_next; 400 m_previous->m_next = this; 401 if (m_next) 402 m_next->m_previous = this; 403 break; 404 } 405 } 406 407 if (!m_previous) { 408 m_next = view().renderQuoteHead(); 409 view().setRenderQuoteHead(this); 410 if (m_next) 411 m_next->m_previous = this; 412 } 413 414 m_isAttached = true; 415 416 for (RenderQuote* quote = this; quote; quote = quote->m_next) 417 quote->updateDepth(); 418 419 ASSERT(!m_next || m_next->m_isAttached); 420 ASSERT(!m_next || m_next->m_previous == this); 421 ASSERT(!m_previous || m_previous->m_isAttached); 422 ASSERT(!m_previous || m_previous->m_next == this); 423} 424 425void RenderQuote::detachQuote() 426{ 427 ASSERT(!m_next || m_next->m_isAttached); 428 ASSERT(!m_previous || m_previous->m_isAttached); 429 if (!m_isAttached) 430 return; 431 if (m_previous) 432 m_previous->m_next = m_next; 433 else 434 view().setRenderQuoteHead(m_next); 435 if (m_next) 436 m_next->m_previous = m_previous; 437 if (!documentBeingDestroyed()) { 438 for (RenderQuote* quote = m_next; quote; quote = quote->m_next) 439 quote->updateDepth(); 440 } 441 m_isAttached = false; 442 m_next = 0; 443 m_previous = 0; 444} 445 446void RenderQuote::updateDepth() 447{ 448 ASSERT(m_isAttached); 449 int depth = 0; 450 if (m_previous) { 451 depth = m_previous->m_depth; 452 if (depth < 0) 453 depth = 0; 454 switch (m_previous->m_type) { 455 case OPEN_QUOTE: 456 case NO_OPEN_QUOTE: 457 depth++; 458 break; 459 case CLOSE_QUOTE: 460 case NO_CLOSE_QUOTE: 461 break; 462 } 463 } 464 switch (m_type) { 465 case OPEN_QUOTE: 466 case NO_OPEN_QUOTE: 467 break; 468 case CLOSE_QUOTE: 469 case NO_CLOSE_QUOTE: 470 depth--; 471 break; 472 } 473 if (m_depth == depth) 474 return; 475 m_depth = depth; 476 updateText(); 477} 478 479} // namespace WebCore 480