blob: b01fdf66d0760b67d35acb021a4b1cac455faa00 [file] [log] [blame]
/*
* Copyright (C) 2011 Nokia Inc. All rights reserved.
* Copyright (C) 2012 Google Inc. All rights reserved.
* Copyright (C) 2013, 2017 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "RenderQuote.h"
#include "RenderBoxModelObjectInlines.h"
#include "RenderObjectInlines.h"
#include "RenderTextFragment.h"
#include "RenderTreeBuilder.h"
#include "RenderView.h"
#include <array>
#include <wtf/StdLibExtras.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/unicode/CharacterNames.h>
namespace WebCore {
using namespace WTF::Unicode;
WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderQuote);
// These strings need to be compared according to "Extended Filtering", as in Section 3.3.2 in RFC4647.
// https://tools.ietf.org/html/rfc4647#page-10
//
// The "checkFurther" field is needed in one specific situation.
// In the quoteTable below, there are lines like:
// { "de"_span , 0x201e, 0x201c, 0x201a, 0x2018 },
// { "de-ch"_span, 0x00ab, 0x00bb, 0x2039, 0x203a },
// Let's say the binary search arbitrarily decided to test our key against the upper line "de" first.
// If the key we're testing against is "de-ch", then we should report "greater than",
// so the binary search will keep searching and eventually find the "de-ch" line.
// However, if the key we're testing against is "de-de", then we should report "equal to",
// because these are the quotes we should use for all "de" except for "de-ch".
struct QuotesForLanguage {
std::span<const char> language;
uint8_t checkFurther { 0 };
char16_t open1 { 0 };
char16_t close1 { 0 };
char16_t open2 { 0 };
char16_t close2 { 0 };
};
// Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quotes
// FIXME: This table is out-of-date.
static constexpr std::array quoteTable {
QuotesForLanguage { "af"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "agq"_span, 0, 0x201e, 0x201d, 0x201a, 0x2019 },
QuotesForLanguage { "ak"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "am"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "ar"_span, 0, 0x201d, 0x201c, 0x2019, 0x2018 },
QuotesForLanguage { "asa"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "az-cyrl"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "bas"_span, 0, 0x00ab, 0x00bb, 0x201e, 0x201c },
QuotesForLanguage { "bem"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "bez"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "bg"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "bm"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "bn"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "br"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "brx"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "bs-cyrl"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "ca"_span, 0, 0x201c, 0x201d, 0x00ab, 0x00bb },
QuotesForLanguage { "cgg"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "chr"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "cs"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "da"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "dav"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "de"_span, 1, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "de-ch"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "dje"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "dua"_span, 0, 0x00ab, 0x00bb, 0x2018, 0x2019 },
QuotesForLanguage { "dyo"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "dz"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ebu"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ee"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "el"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "en"_span, 1, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "en-gb"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "es"_span, 0, 0x201c, 0x201d, 0x00ab, 0x00bb },
QuotesForLanguage { "et"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "eu"_span, 0, 0x201c, 0x201d, 0x00ab, 0x00bb },
QuotesForLanguage { "ewo"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "fa"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "ff"_span, 0, 0x201e, 0x201d, 0x201a, 0x2019 },
QuotesForLanguage { "fi"_span, 0, 0x201d, 0x201d, 0x2019, 0x2019 },
QuotesForLanguage { "fr"_span, 2, 0x00ab, 0x00bb, 0x00ab, 0x00bb },
QuotesForLanguage { "fr-ca"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "fr-ch"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "gsw"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "gu"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "guz"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ha"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "he"_span, 0, 0x0022, 0x0022, 0x0027, 0x0027 },
QuotesForLanguage { "hi"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "hr"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "hu"_span, 0, 0x201e, 0x201d, 0x00bb, 0x00ab },
QuotesForLanguage { "id"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ig"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "it"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "ja"_span, 0, 0x300c, 0x300d, 0x300e, 0x300f },
QuotesForLanguage { "jgo"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "jmc"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "kab"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "kam"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "kde"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "kea"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "khq"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ki"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "kkj"_span, 0, 0x00ab, 0x00bb, 0x2039, 0x203a },
QuotesForLanguage { "kln"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "km"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "kn"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ko"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ksb"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ksf"_span, 0, 0x00ab, 0x00bb, 0x2018, 0x2019 },
QuotesForLanguage { "lag"_span, 0, 0x201d, 0x201d, 0x2019, 0x2019 },
QuotesForLanguage { "lg"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ln"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "lo"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "lt"_span, 0, 0x201e, 0x201c, 0x201e, 0x201c },
QuotesForLanguage { "lu"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "luo"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "luy"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "lv"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mas"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mer"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mfe"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mg"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "mgo"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mk"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "ml"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mr"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ms"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "mua"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "my"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "naq"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "nb"_span, 0, 0x00ab, 0x00bb, 0x2018, 0x2019 },
QuotesForLanguage { "nd"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "nl"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "nmg"_span, 0, 0x201e, 0x201d, 0x00ab, 0x00bb },
QuotesForLanguage { "nn"_span, 0, 0x00ab, 0x00bb, 0x2018, 0x2019 },
QuotesForLanguage { "nnh"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "nus"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "nyn"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "pl"_span, 0, 0x201e, 0x201d, 0x00ab, 0x00bb },
QuotesForLanguage { "pt"_span, 1, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "pt-pt"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "rn"_span, 0, 0x201d, 0x201d, 0x2019, 0x2019 },
QuotesForLanguage { "ro"_span, 0, 0x201e, 0x201d, 0x00ab, 0x00bb },
QuotesForLanguage { "rof"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ru"_span, 0, 0x00ab, 0x00bb, 0x201e, 0x201c },
QuotesForLanguage { "rw"_span, 0, 0x00ab, 0x00bb, 0x2018, 0x2019 },
QuotesForLanguage { "rwk"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "saq"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "sbp"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "seh"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ses"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "sg"_span, 0, 0x00ab, 0x00bb, 0x201c, 0x201d },
QuotesForLanguage { "shi"_span, 1, 0x00ab, 0x00bb, 0x201e, 0x201d },
QuotesForLanguage { "shi-tfng"_span, 0, 0x00ab, 0x00bb, 0x201e, 0x201d },
QuotesForLanguage { "si"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "sk"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "sl"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "sn"_span, 0, 0x201d, 0x201d, 0x2019, 0x2019 },
QuotesForLanguage { "so"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "sq"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "sr"_span, 1, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "sr-latn"_span, 0, 0x201e, 0x201c, 0x201a, 0x2018 },
QuotesForLanguage { "sv"_span, 0, 0x201d, 0x201d, 0x2019, 0x2019 },
QuotesForLanguage { "sw"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "swc"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ta"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "te"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "teo"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "th"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "ti-er"_span, 0, 0x2018, 0x2019, 0x201c, 0x201d },
QuotesForLanguage { "to"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "tr"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "twq"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "tzm"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "uk"_span, 0, 0x00ab, 0x00bb, 0x201e, 0x201c },
QuotesForLanguage { "ur"_span, 0, 0x201d, 0x201c, 0x2019, 0x2018 },
QuotesForLanguage { "vai"_span, 1, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "vai-latn"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "vi"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "vun"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "xh"_span, 0, 0x2018, 0x2019, 0x201c, 0x201d },
QuotesForLanguage { "xog"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "yav"_span, 0, 0x00ab, 0x00bb, 0x00ab, 0x00bb },
QuotesForLanguage { "yo"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "zh"_span, 1, 0x201c, 0x201d, 0x2018, 0x2019 },
QuotesForLanguage { "zh-hant"_span, 0, 0x300c, 0x300d, 0x300e, 0x300f },
QuotesForLanguage { "zu"_span, 0, 0x201c, 0x201d, 0x2018, 0x2019 },
};
RenderQuote::RenderQuote(Document& document, RenderStyle&& style, QuoteType quote)
: RenderInline(Type::Quote, document, WTF::move(style))
, m_type(quote)
, m_text(emptyString())
{
ASSERT(isRenderQuote());
}
// Do not add any code in below destructor. Add it to willBeDestroyed() instead.
RenderQuote::~RenderQuote() = default;
void RenderQuote::insertedIntoTree()
{
RenderInline::insertedIntoTree();
view().setHasQuotesNeedingUpdate(true);
}
void RenderQuote::willBeRemovedFromTree()
{
view().setHasQuotesNeedingUpdate(true);
RenderInline::willBeRemovedFromTree();
}
void RenderQuote::styleDidChange(Style::Difference diff, const RenderStyle* oldStyle)
{
RenderInline::styleDidChange(diff, oldStyle);
if (diff >= Style::DifferenceResult::Layout) {
m_needsTextUpdate = true;
view().setHasQuotesNeedingUpdate(true);
}
}
constexpr unsigned maxDistinctQuoteCharacters = 16;
#if ASSERT_ENABLED
static void checkNumberOfDistinctQuoteCharacters(char16_t character)
{
ASSERT(character);
static std::array<char16_t, maxDistinctQuoteCharacters> distinctQuoteCharacters;
for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
if (distinctQuoteCharacters[i] == character)
return;
if (!distinctQuoteCharacters[i]) {
distinctQuoteCharacters[i] = character;
return;
}
}
ASSERT_NOT_REACHED();
}
#endif // ASSERT_ENABLED
struct SubtagComparison {
size_t keyLength;
size_t keyContinue;
size_t rangeLength;
size_t rangeContinue;
std::strong_ordering comparison { std::strong_ordering::equal };
};
static SubtagComparison subtagCompare(std::span<const char> key, std::span<const char> range)
{
SubtagComparison result;
result.keyLength = key.size();
result.keyContinue = result.keyLength;
if (size_t hyphenIndex = find(key, '-'); hyphenIndex != notFound) {
result.keyLength = hyphenIndex;
result.keyContinue = result.keyLength + 1;
}
result.rangeLength = range.size();
result.rangeContinue = result.rangeLength;
if (size_t hyphenIndex = find(range, '-'); hyphenIndex != notFound) {
result.rangeLength = hyphenIndex;
result.rangeContinue = result.rangeLength + 1;
}
if (result.keyLength == result.rangeLength)
result.comparison = compareSpans(key.first(result.keyLength), range.first(result.keyLength));
else
result.comparison = compareSpans(key, range);
return result;
}
static std::strong_ordering quoteTableLanguageComparisonFunction(const QuotesForLanguage& key, std::span<const QuotesForLanguage> range)
{
// These strings need to be compared according to "Extended Filtering", as in Section 3.3.2 in RFC4647.
// https://tools.ietf.org/html/rfc4647#page-10
//
// We can exploit a few things here to improve perf:
// 1. The first subtag must be matched exactly
// 2. All the ranges have either 1 or 2 subtags
// 3. None of the subtags in any of the ranges are wildcards
//
// Also, see the comment just above the QuotesForLanguage struct.
auto firstSubtagComparison = subtagCompare(key.language, range[0].language);
if (firstSubtagComparison.keyLength != firstSubtagComparison.rangeLength)
return firstSubtagComparison.comparison;
if (is_neq(firstSubtagComparison.comparison))
return firstSubtagComparison.comparison;
for (auto& checkFurtherRange : range.subspan(1)) {
if (is_eq(quoteTableLanguageComparisonFunction(key, singleElementSpan(checkFurtherRange)))) {
// Tell the binary search to check later in the array of ranges, to eventually find the match we just found here.
return std::strong_ordering::greater;
}
}
for (size_t keyOffset = firstSubtagComparison.keyContinue; ;) {
auto nextSubtagComparison = subtagCompare(key.language.subspan(keyOffset), range[0].language.subspan(firstSubtagComparison.rangeContinue));
if (!nextSubtagComparison.rangeLength) {
// E.g. The key is "zh-Hans" and the range is "zh".
return std::strong_ordering::equal;
}
if (!nextSubtagComparison.keyLength) {
// E.g. the key is "zh" and the range is "zh-Hant".
return nextSubtagComparison.comparison;
}
if (nextSubtagComparison.keyLength == 1) {
// E.g. the key is "zh-x-Hant" and the range is "zh-Hant".
// We want to try to find the range "zh", so tell the binary search to check earlier in the array of ranges.
return std::strong_ordering::less;
}
if (nextSubtagComparison.keyLength == nextSubtagComparison.rangeLength && is_eq(nextSubtagComparison.comparison)) {
// E.g. the key is "de-Latn-ch" and the range is "de-ch".
return std::strong_ordering::equal;
}
keyOffset += nextSubtagComparison.keyContinue;
}
}
static const QuotesForLanguage* binaryFindQuotes(const QuotesForLanguage& key, std::span<const QuotesForLanguage> subrange = quoteTable)
{
if (subrange.empty())
return nullptr;
auto& middle = subrange[subrange.size() / 2];
auto comparison = quoteTableLanguageComparisonFunction(key, std::span { quoteTable }.subspan(&middle - quoteTable.data(), 1 + middle.checkFurther));
if (is_eq(comparison))
return &middle;
if (is_lt(comparison))
return binaryFindQuotes(key, subrange.first(subrange.size() / 2));
return binaryFindQuotes(key, subrange.subspan(subrange.size() / 2 + 1));
}
static const QuotesForLanguage* quotesForLanguage(const String& language)
{
#if ASSERT_ENABLED
// One time check that the table meets the constraints that the code below relies on.
static bool didOneTimeCheck = false;
if (!didOneTimeCheck) {
didOneTimeCheck = true;
checkNumberOfDistinctQuoteCharacters(quotationMark);
checkNumberOfDistinctQuoteCharacters(apostrophe);
for (unsigned i = 0; i < std::size(quoteTable); ++i) {
if (i)
ASSERT(is_lt(compareSpans(quoteTable[i - 1].language, quoteTable[i].language)));
for (auto character : quoteTable[i].language)
ASSERT(isASCIILower(character) || character == '-');
checkNumberOfDistinctQuoteCharacters(quoteTable[i].open1);
checkNumberOfDistinctQuoteCharacters(quoteTable[i].close1);
checkNumberOfDistinctQuoteCharacters(quoteTable[i].open2);
checkNumberOfDistinctQuoteCharacters(quoteTable[i].close2);
}
}
#endif // ASSERT_ENABLED
unsigned length = language.length();
if (!length)
return nullptr;
Vector<char> languageKeyBuffer(length);
for (unsigned i = 0; i < length; ++i) {
char16_t character = toASCIILower(language[i]);
if (!(isASCIILower(character) || character == '-'))
return nullptr;
languageKeyBuffer[i] = static_cast<char>(character);
}
return binaryFindQuotes({ languageKeyBuffer.span() });
}
static StringImpl* stringForQuoteCharacter(char16_t character)
{
// Use linear search because there is a small number of distinct characters, thus binary search is unneeded.
ASSERT(character);
struct StringForCharacter {
char16_t character;
StringImpl* string;
};
static std::array<StringForCharacter, maxDistinctQuoteCharacters> strings;
for (auto& string : strings) {
if (string.character == character)
return string.string;
if (!string.character) {
string.character = character;
string.string = &StringImpl::create8BitIfPossible(span(character)).leakRef();
return string.string;
}
}
ASSERT_NOT_REACHED();
return StringImpl::empty();
}
static inline StringImpl* quotationMarkString()
{
static NeverDestroyed<Ref<StringImpl>> quotationMarkString { *stringForQuoteCharacter(quotationMark) };
return quotationMarkString->ptr();
}
static inline StringImpl* apostropheString()
{
static NeverDestroyed<Ref<StringImpl>> apostropheString { *stringForQuoteCharacter(apostrophe) };
return apostropheString->ptr();
}
void RenderQuote::updateTextRenderer(RenderTreeBuilder& builder)
{
ASSERT_WITH_SECURITY_IMPLICATION(document().inRenderTreeUpdate());
String text = computeText();
if (m_text == text)
return;
m_text = text;
if (auto* renderText = dynamicDowncast<RenderTextFragment>(lastChild())) {
renderText->setContentString(m_text);
return;
}
builder.attach(*this, createRenderer<RenderTextFragment>(document(), m_text));
}
String RenderQuote::computeText() const
{
if (m_depth < 0)
return emptyString();
bool isOpenQuote = false;
switch (m_type) {
case QuoteType::NoOpenQuote:
case QuoteType::NoCloseQuote:
return emptyString();
case QuoteType::OpenQuote:
isOpenQuote = true;
[[fallthrough]];
case QuoteType::CloseQuote:
if (!style().quotes().isAuto())
return isOpenQuote ? style().quotes().openQuote(m_depth).impl() : style().quotes().closeQuote(m_depth).impl();
if (const auto* quotes = quotesForLanguage(Style::toPlatform(style().computedLocale())))
return stringForQuoteCharacter(isOpenQuote ? (m_depth ? quotes->open2 : quotes->open1) : (m_depth ? quotes->close2 : quotes->close1));
// FIXME: Should the default be the quotes for "en" rather than straight quotes?
// (According to https://html.spec.whatwg.org/multipage/rendering.html#quotes, the answer is "yes".)
return m_depth ? apostropheString() : quotationMarkString();
}
ASSERT_NOT_REACHED();
return emptyString();
}
bool RenderQuote::isOpen() const
{
switch (m_type) {
case QuoteType::OpenQuote:
case QuoteType::NoOpenQuote:
return true;
case QuoteType::CloseQuote:
case QuoteType::NoCloseQuote:
return false;
}
ASSERT_NOT_REACHED();
return false;
}
void RenderQuote::updateRenderer(RenderTreeBuilder& builder, RenderQuote* previousQuote)
{
ASSERT_WITH_SECURITY_IMPLICATION(document().inRenderTreeUpdate());
int depth = -1;
if (previousQuote) {
depth = previousQuote->m_depth;
if (previousQuote->isOpen())
++depth;
}
if (!isOpen())
--depth;
else if (depth < 0)
depth = 0;
if (m_depth == depth && !m_needsTextUpdate)
return;
m_depth = depth;
m_needsTextUpdate = false;
updateTextRenderer(builder);
}
} // namespace WebCore