1/*
2 * Copyright (C) 2011 Nokia Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Copyright (C) 2013, 2017 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 "RenderTreeBuilder.h"
29#include "RenderView.h"
30#include <wtf/IsoMallocInlines.h>
31#include <wtf/unicode/CharacterNames.h>
32
33namespace WebCore {
34using namespace WTF::Unicode;
35
36WTF_MAKE_ISO_ALLOCATED_IMPL(RenderQuote);
37
38RenderQuote::RenderQuote(Document& document, RenderStyle&& style, QuoteType quote)
39 : RenderInline(document, WTFMove(style))
40 , m_type(quote)
41 , m_text(emptyString())
42{
43}
44
45RenderQuote::~RenderQuote()
46{
47 // Do not add any code here. Add it to willBeDestroyed() instead.
48}
49
50void RenderQuote::insertedIntoTree()
51{
52 RenderInline::insertedIntoTree();
53 view().setHasQuotesNeedingUpdate(true);
54}
55
56void RenderQuote::willBeRemovedFromTree()
57{
58 view().setHasQuotesNeedingUpdate(true);
59 RenderInline::willBeRemovedFromTree();
60}
61
62void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
63{
64 RenderInline::styleDidChange(diff, oldStyle);
65 if (diff >= StyleDifference::Layout) {
66 m_needsTextUpdate = true;
67 view().setHasQuotesNeedingUpdate(true);
68 }
69}
70
71const unsigned maxDistinctQuoteCharacters = 16;
72
73#if !ASSERT_DISABLED
74
75static void checkNumberOfDistinctQuoteCharacters(UChar character)
76{
77 ASSERT(character);
78 static UChar distinctQuoteCharacters[maxDistinctQuoteCharacters];
79 for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
80 if (distinctQuoteCharacters[i] == character)
81 return;
82 if (!distinctQuoteCharacters[i]) {
83 distinctQuoteCharacters[i] = character;
84 return;
85 }
86 }
87 ASSERT_NOT_REACHED();
88}
89
90#endif
91
92struct QuotesForLanguage {
93 const char* language;
94 UChar open1;
95 UChar close1;
96 UChar open2;
97 UChar close2;
98};
99
100static int quoteTableLanguageComparisonFunction(const void* a, const void* b)
101{
102 return strcmp(static_cast<const QuotesForLanguage*>(a)->language,
103 static_cast<const QuotesForLanguage*>(b)->language);
104}
105
106static const QuotesForLanguage* quotesForLanguage(const String& language)
107{
108 // Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quotes
109 static const QuotesForLanguage quoteTable[] = {
110 { "af", 0x201c, 0x201d, 0x2018, 0x2019 },
111 { "agq", 0x201e, 0x201d, 0x201a, 0x2019 },
112 { "ak", 0x201c, 0x201d, 0x2018, 0x2019 },
113 { "am", 0x00ab, 0x00bb, 0x2039, 0x203a },
114 { "ar", 0x201d, 0x201c, 0x2019, 0x2018 },
115 { "asa", 0x201c, 0x201d, 0x2018, 0x2019 },
116 { "az-cyrl", 0x00ab, 0x00bb, 0x2039, 0x203a },
117 { "bas", 0x00ab, 0x00bb, 0x201e, 0x201c },
118 { "bem", 0x201c, 0x201d, 0x2018, 0x2019 },
119 { "bez", 0x201c, 0x201d, 0x2018, 0x2019 },
120 { "bg", 0x201e, 0x201c, 0x201a, 0x2018 },
121 { "bm", 0x00ab, 0x00bb, 0x201c, 0x201d },
122 { "bn", 0x201c, 0x201d, 0x2018, 0x2019 },
123 { "br", 0x00ab, 0x00bb, 0x2039, 0x203a },
124 { "brx", 0x201c, 0x201d, 0x2018, 0x2019 },
125 { "bs-cyrl", 0x201e, 0x201c, 0x201a, 0x2018 },
126 { "ca", 0x201c, 0x201d, 0x00ab, 0x00bb },
127 { "cgg", 0x201c, 0x201d, 0x2018, 0x2019 },
128 { "chr", 0x201c, 0x201d, 0x2018, 0x2019 },
129 { "cs", 0x201e, 0x201c, 0x201a, 0x2018 },
130 { "da", 0x201c, 0x201d, 0x2018, 0x2019 },
131 { "dav", 0x201c, 0x201d, 0x2018, 0x2019 },
132 { "de", 0x201e, 0x201c, 0x201a, 0x2018 },
133 { "de-ch", 0x00ab, 0x00bb, 0x2039, 0x203a },
134 { "dje", 0x201c, 0x201d, 0x2018, 0x2019 },
135 { "dua", 0x00ab, 0x00bb, 0x2018, 0x2019 },
136 { "dyo", 0x00ab, 0x00bb, 0x201c, 0x201d },
137 { "dz", 0x201c, 0x201d, 0x2018, 0x2019 },
138 { "ebu", 0x201c, 0x201d, 0x2018, 0x2019 },
139 { "ee", 0x201c, 0x201d, 0x2018, 0x2019 },
140 { "el", 0x00ab, 0x00bb, 0x201c, 0x201d },
141 { "en", 0x201c, 0x201d, 0x2018, 0x2019 },
142 { "en-gb", 0x201c, 0x201d, 0x2018, 0x2019 },
143 { "es", 0x201c, 0x201d, 0x00ab, 0x00bb },
144 { "et", 0x201e, 0x201c, 0x201a, 0x2018 },
145 { "eu", 0x201c, 0x201d, 0x00ab, 0x00bb },
146 { "ewo", 0x00ab, 0x00bb, 0x201c, 0x201d },
147 { "fa", 0x00ab, 0x00bb, 0x2039, 0x203a },
148 { "ff", 0x201e, 0x201d, 0x201a, 0x2019 },
149 { "fi", 0x201d, 0x201d, 0x2019, 0x2019 },
150 { "fr", 0x00ab, 0x00bb, 0x00ab, 0x00bb },
151 { "fr-ca", 0x00ab, 0x00bb, 0x2039, 0x203a },
152 { "fr-ch", 0x00ab, 0x00bb, 0x2039, 0x203a },
153 { "gsw", 0x00ab, 0x00bb, 0x2039, 0x203a },
154 { "gu", 0x201c, 0x201d, 0x2018, 0x2019 },
155 { "guz", 0x201c, 0x201d, 0x2018, 0x2019 },
156 { "ha", 0x201c, 0x201d, 0x2018, 0x2019 },
157 { "he", 0x0022, 0x0022, 0x0027, 0x0027 },
158 { "hi", 0x201c, 0x201d, 0x2018, 0x2019 },
159 { "hr", 0x201e, 0x201c, 0x201a, 0x2018 },
160 { "hu", 0x201e, 0x201d, 0x00bb, 0x00ab },
161 { "id", 0x201c, 0x201d, 0x2018, 0x2019 },
162 { "ig", 0x201c, 0x201d, 0x2018, 0x2019 },
163 { "it", 0x00ab, 0x00bb, 0x201c, 0x201d },
164 { "ja", 0x300c, 0x300d, 0x300e, 0x300f },
165 { "jgo", 0x00ab, 0x00bb, 0x2039, 0x203a },
166 { "jmc", 0x201c, 0x201d, 0x2018, 0x2019 },
167 { "kab", 0x00ab, 0x00bb, 0x201c, 0x201d },
168 { "kam", 0x201c, 0x201d, 0x2018, 0x2019 },
169 { "kde", 0x201c, 0x201d, 0x2018, 0x2019 },
170 { "kea", 0x201c, 0x201d, 0x2018, 0x2019 },
171 { "khq", 0x201c, 0x201d, 0x2018, 0x2019 },
172 { "ki", 0x201c, 0x201d, 0x2018, 0x2019 },
173 { "kkj", 0x00ab, 0x00bb, 0x2039, 0x203a },
174 { "kln", 0x201c, 0x201d, 0x2018, 0x2019 },
175 { "km", 0x201c, 0x201d, 0x2018, 0x2019 },
176 { "kn", 0x201c, 0x201d, 0x2018, 0x2019 },
177 { "ko", 0x201c, 0x201d, 0x2018, 0x2019 },
178 { "ksb", 0x201c, 0x201d, 0x2018, 0x2019 },
179 { "ksf", 0x00ab, 0x00bb, 0x2018, 0x2019 },
180 { "lag", 0x201d, 0x201d, 0x2019, 0x2019 },
181 { "lg", 0x201c, 0x201d, 0x2018, 0x2019 },
182 { "ln", 0x201c, 0x201d, 0x2018, 0x2019 },
183 { "lo", 0x201c, 0x201d, 0x2018, 0x2019 },
184 { "lt", 0x201e, 0x201c, 0x201e, 0x201c },
185 { "lu", 0x201c, 0x201d, 0x2018, 0x2019 },
186 { "luo", 0x201c, 0x201d, 0x2018, 0x2019 },
187 { "luy", 0x201e, 0x201c, 0x201a, 0x2018 },
188 { "lv", 0x201c, 0x201d, 0x2018, 0x2019 },
189 { "mas", 0x201c, 0x201d, 0x2018, 0x2019 },
190 { "mer", 0x201c, 0x201d, 0x2018, 0x2019 },
191 { "mfe", 0x201c, 0x201d, 0x2018, 0x2019 },
192 { "mg", 0x00ab, 0x00bb, 0x201c, 0x201d },
193 { "mgo", 0x201c, 0x201d, 0x2018, 0x2019 },
194 { "mk", 0x201e, 0x201c, 0x201a, 0x2018 },
195 { "ml", 0x201c, 0x201d, 0x2018, 0x2019 },
196 { "mr", 0x201c, 0x201d, 0x2018, 0x2019 },
197 { "ms", 0x201c, 0x201d, 0x2018, 0x2019 },
198 { "mua", 0x00ab, 0x00bb, 0x201c, 0x201d },
199 { "my", 0x201c, 0x201d, 0x2018, 0x2019 },
200 { "naq", 0x201c, 0x201d, 0x2018, 0x2019 },
201 { "nb", 0x00ab, 0x00bb, 0x2018, 0x2019 },
202 { "nd", 0x201c, 0x201d, 0x2018, 0x2019 },
203 { "nl", 0x201c, 0x201d, 0x2018, 0x2019 },
204 { "nmg", 0x201e, 0x201d, 0x00ab, 0x00bb },
205 { "nn", 0x00ab, 0x00bb, 0x2018, 0x2019 },
206 { "nnh", 0x00ab, 0x00bb, 0x201c, 0x201d },
207 { "nus", 0x201c, 0x201d, 0x2018, 0x2019 },
208 { "nyn", 0x201c, 0x201d, 0x2018, 0x2019 },
209 { "pl", 0x201e, 0x201d, 0x00ab, 0x00bb },
210 { "pt", 0x201c, 0x201d, 0x2018, 0x2019 },
211 { "pt-pt", 0x00ab, 0x00bb, 0x201c, 0x201d },
212 { "rn", 0x201d, 0x201d, 0x2019, 0x2019 },
213 { "ro", 0x201e, 0x201d, 0x00ab, 0x00bb },
214 { "rof", 0x201c, 0x201d, 0x2018, 0x2019 },
215 { "ru", 0x00ab, 0x00bb, 0x201e, 0x201c },
216 { "rw", 0x00ab, 0x00bb, 0x2018, 0x2019 },
217 { "rwk", 0x201c, 0x201d, 0x2018, 0x2019 },
218 { "saq", 0x201c, 0x201d, 0x2018, 0x2019 },
219 { "sbp", 0x201c, 0x201d, 0x2018, 0x2019 },
220 { "seh", 0x201c, 0x201d, 0x2018, 0x2019 },
221 { "ses", 0x201c, 0x201d, 0x2018, 0x2019 },
222 { "sg", 0x00ab, 0x00bb, 0x201c, 0x201d },
223 { "shi", 0x00ab, 0x00bb, 0x201e, 0x201d },
224 { "shi-tfng", 0x00ab, 0x00bb, 0x201e, 0x201d },
225 { "si", 0x201c, 0x201d, 0x2018, 0x2019 },
226 { "sk", 0x201e, 0x201c, 0x201a, 0x2018 },
227 { "sl", 0x201e, 0x201c, 0x201a, 0x2018 },
228 { "sn", 0x201d, 0x201d, 0x2019, 0x2019 },
229 { "so", 0x201c, 0x201d, 0x2018, 0x2019 },
230 { "sq", 0x201e, 0x201c, 0x201a, 0x2018 },
231 { "sr", 0x201e, 0x201c, 0x201a, 0x2018 },
232 { "sr-latn", 0x201e, 0x201c, 0x201a, 0x2018 },
233 { "sv", 0x201d, 0x201d, 0x2019, 0x2019 },
234 { "sw", 0x201c, 0x201d, 0x2018, 0x2019 },
235 { "swc", 0x201c, 0x201d, 0x2018, 0x2019 },
236 { "ta", 0x201c, 0x201d, 0x2018, 0x2019 },
237 { "te", 0x201c, 0x201d, 0x2018, 0x2019 },
238 { "teo", 0x201c, 0x201d, 0x2018, 0x2019 },
239 { "th", 0x201c, 0x201d, 0x2018, 0x2019 },
240 { "ti-er", 0x2018, 0x2019, 0x201c, 0x201d },
241 { "to", 0x201c, 0x201d, 0x2018, 0x2019 },
242 { "tr", 0x201c, 0x201d, 0x2018, 0x2019 },
243 { "twq", 0x201c, 0x201d, 0x2018, 0x2019 },
244 { "tzm", 0x201c, 0x201d, 0x2018, 0x2019 },
245 { "uk", 0x00ab, 0x00bb, 0x201e, 0x201c },
246 { "ur", 0x201d, 0x201c, 0x2019, 0x2018 },
247 { "vai", 0x201c, 0x201d, 0x2018, 0x2019 },
248 { "vai-latn", 0x201c, 0x201d, 0x2018, 0x2019 },
249 { "vi", 0x201c, 0x201d, 0x2018, 0x2019 },
250 { "vun", 0x201c, 0x201d, 0x2018, 0x2019 },
251 { "xh", 0x2018, 0x2019, 0x201c, 0x201d },
252 { "xog", 0x201c, 0x201d, 0x2018, 0x2019 },
253 { "yav", 0x00ab, 0x00bb, 0x00ab, 0x00bb },
254 { "yo", 0x201c, 0x201d, 0x2018, 0x2019 },
255 { "zh", 0x201c, 0x201d, 0x2018, 0x2019 },
256 { "zh-hant", 0x300c, 0x300d, 0x300e, 0x300f },
257 { "zu", 0x201c, 0x201d, 0x2018, 0x2019 },
258 };
259
260 const unsigned maxLanguageLength = 8;
261
262#if !ASSERT_DISABLED
263 // One time check that the table meets the constraints that the code below relies on.
264
265 static bool didOneTimeCheck = false;
266 if (!didOneTimeCheck) {
267 didOneTimeCheck = true;
268
269 checkNumberOfDistinctQuoteCharacters(quotationMark);
270 checkNumberOfDistinctQuoteCharacters(apostrophe);
271
272 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(quoteTable); ++i) {
273 ASSERT(strlen(quoteTable[i].language) <= maxLanguageLength);
274
275 if (i)
276 ASSERT(strcmp(quoteTable[i - 1].language, quoteTable[i].language) < 0);
277
278 for (unsigned j = 0; UChar character = quoteTable[i].language[j]; ++j)
279 ASSERT(isASCIILower(character) || character == '-');
280
281 checkNumberOfDistinctQuoteCharacters(quoteTable[i].open1);
282 checkNumberOfDistinctQuoteCharacters(quoteTable[i].close1);
283 checkNumberOfDistinctQuoteCharacters(quoteTable[i].open2);
284 checkNumberOfDistinctQuoteCharacters(quoteTable[i].close2);
285 }
286 }
287#endif
288
289 unsigned length = language.length();
290 if (!length || length > maxLanguageLength)
291 return 0;
292
293 char languageKeyBuffer[maxLanguageLength + 1];
294 for (unsigned i = 0; i < length; ++i) {
295 UChar character = toASCIILower(language[i]);
296 if (!(isASCIILower(character) || character == '-'))
297 return 0;
298 languageKeyBuffer[i] = static_cast<char>(character);
299 }
300 languageKeyBuffer[length] = 0;
301
302 QuotesForLanguage languageKey = { languageKeyBuffer, 0, 0, 0, 0 };
303
304 return static_cast<const QuotesForLanguage*>(bsearch(&languageKey,
305 quoteTable, WTF_ARRAY_LENGTH(quoteTable), sizeof(quoteTable[0]), quoteTableLanguageComparisonFunction));
306}
307
308static StringImpl* stringForQuoteCharacter(UChar character)
309{
310 // Use linear search because there is a small number of distinct characters, thus binary search is unneeded.
311 ASSERT(character);
312 struct StringForCharacter {
313 UChar character;
314 StringImpl* string;
315 };
316 static StringForCharacter strings[maxDistinctQuoteCharacters];
317 for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
318 if (strings[i].character == character)
319 return strings[i].string;
320 if (!strings[i].character) {
321 strings[i].character = character;
322 strings[i].string = &StringImpl::create8BitIfPossible(&character, 1).leakRef();
323 return strings[i].string;
324 }
325 }
326 ASSERT_NOT_REACHED();
327 return StringImpl::empty();
328}
329
330static inline StringImpl* quotationMarkString()
331{
332 static StringImpl* quotationMarkString = stringForQuoteCharacter(quotationMark);
333 return quotationMarkString;
334}
335
336static inline StringImpl* apostropheString()
337{
338 static StringImpl* apostropheString = stringForQuoteCharacter(apostrophe);
339 return apostropheString;
340}
341
342static RenderTextFragment* quoteTextRenderer(RenderObject* lastChild)
343{
344 if (!lastChild)
345 return nullptr;
346
347 if (!is<RenderTextFragment>(lastChild))
348 return nullptr;
349
350 return downcast<RenderTextFragment>(lastChild);
351}
352
353void RenderQuote::updateTextRenderer(RenderTreeBuilder& builder)
354{
355 ASSERT_WITH_SECURITY_IMPLICATION(document().inRenderTreeUpdate());
356 String text = computeText();
357 if (m_text == text)
358 return;
359 m_text = text;
360 if (auto* renderText = quoteTextRenderer(lastChild())) {
361 renderText->setContentString(m_text);
362 renderText->dirtyLineBoxes(false);
363 return;
364 }
365 builder.attach(*this, createRenderer<RenderTextFragment>(document(), m_text));
366}
367
368String RenderQuote::computeText() const
369{
370 if (m_depth < 0)
371 return emptyString();
372 bool isOpenQuote = false;
373 switch (m_type) {
374 case QuoteType::NoOpenQuote:
375 case QuoteType::NoCloseQuote:
376 return emptyString();
377 case QuoteType::OpenQuote:
378 isOpenQuote = true;
379 FALLTHROUGH;
380 case QuoteType::CloseQuote:
381 if (const QuotesData* quotes = style().quotes())
382 return isOpenQuote ? quotes->openQuote(m_depth).impl() : quotes->closeQuote(m_depth).impl();
383 if (const QuotesForLanguage* quotes = quotesForLanguage(style().locale()))
384 return stringForQuoteCharacter(isOpenQuote ? (m_depth ? quotes->open2 : quotes->open1) : (m_depth ? quotes->close2 : quotes->close1));
385 // FIXME: Should the default be the quotes for "en" rather than straight quotes?
386 return m_depth ? apostropheString() : quotationMarkString();
387 }
388 ASSERT_NOT_REACHED();
389 return emptyString();
390}
391
392bool RenderQuote::isOpen() const
393{
394 switch (m_type) {
395 case QuoteType::OpenQuote:
396 case QuoteType::NoOpenQuote:
397 return true;
398 case QuoteType::CloseQuote:
399 case QuoteType::NoCloseQuote:
400 return false;
401 }
402 ASSERT_NOT_REACHED();
403 return false;
404}
405
406void RenderQuote::updateRenderer(RenderTreeBuilder& builder, RenderQuote* previousQuote)
407{
408 ASSERT_WITH_SECURITY_IMPLICATION(document().inRenderTreeUpdate());
409 int depth = -1;
410 if (previousQuote) {
411 depth = previousQuote->m_depth;
412 if (previousQuote->isOpen())
413 ++depth;
414 }
415
416 if (!isOpen())
417 --depth;
418 else if (depth < 0)
419 depth = 0;
420
421 if (m_depth == depth && !m_needsTextUpdate)
422 return;
423
424 m_depth = depth;
425 m_needsTextUpdate = false;
426 updateTextRenderer(builder);
427}
428
429} // namespace WebCore
430