#include "config.h"
#include "FontCascade.h"
#include "CharacterProperties.h"
#include "ComplexTextController.h"
#include "DisplayListRecorder.h"
#include "FloatRect.h"
#include "FontCache.h"
#include "GlyphBuffer.h"
#include "GraphicsContext.h"
#include "LayoutRect.h"
#include "SurrogatePairAwareTextIterator.h"
#include "TextRun.h"
#include "WidthIterator.h"
#include <wtf/MainThread.h>
#include <wtf/MathExtras.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/AtomStringHash.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
using namespace WTF::Unicode;
static bool useBackslashAsYenSignForFamily(const AtomString& family)
{
if (family.isEmpty())
return false;
static const auto set = makeNeverDestroyed([] {
HashSet<AtomString> set;
auto add = [&set] (const char* name, std::initializer_list<UChar> unicodeName) {
unsigned nameLength = strlen(name);
set.add(AtomString { name, nameLength, AtomString::ConstructFromLiteral });
unsigned unicodeNameLength = unicodeName.size();
set.add(AtomString { unicodeName.begin(), unicodeNameLength });
};
add("MS PGothic", { 0xFF2D, 0xFF33, 0x0020, 0xFF30, 0x30B4, 0x30B7, 0x30C3, 0x30AF });
add("MS PMincho", { 0xFF2D, 0xFF33, 0x0020, 0xFF30, 0x660E, 0x671D });
add("MS Gothic", { 0xFF2D, 0xFF33, 0x0020, 0x30B4, 0x30B7, 0x30C3, 0x30AF });
add("MS Mincho", { 0xFF2D, 0xFF33, 0x0020, 0x660E, 0x671D });
add("Meiryo", { 0x30E1, 0x30A4, 0x30EA, 0x30AA });
return set;
}());
return set.get().contains(family);
}
FontCascade::CodePath FontCascade::s_codePath = Auto;
FontCascade::FontCascade()
{
}
FontCascade::FontCascade(FontCascadeDescription&& fd, float letterSpacing, float wordSpacing)
: m_fontDescription(WTFMove(fd))
, m_letterSpacing(letterSpacing)
, m_wordSpacing(wordSpacing)
, m_useBackslashAsYenSymbol(useBackslashAsYenSignForFamily(m_fontDescription.firstFamily()))
, m_enableKerning(computeEnableKerning())
, m_requiresShaping(computeRequiresShaping())
{
}
FontCascade::FontCascade(const FontPlatformData& fontData, FontSmoothingMode fontSmoothingMode)
: m_fonts(FontCascadeFonts::createForPlatformFont(fontData))
, m_enableKerning(computeEnableKerning())
, m_requiresShaping(computeRequiresShaping())
{
m_fontDescription.setFontSmoothing(fontSmoothingMode);
#if PLATFORM(IOS_FAMILY)
m_fontDescription.setSpecifiedSize(CTFontGetSize(fontData.font()));
m_fontDescription.setComputedSize(CTFontGetSize(fontData.font()));
m_fontDescription.setIsItalic(CTFontGetSymbolicTraits(fontData.font()) & kCTFontTraitItalic);
m_fontDescription.setWeight((CTFontGetSymbolicTraits(fontData.font()) & kCTFontTraitBold) ? boldWeightValue() : normalWeightValue());
#endif
}
FontCascade::FontCascade(const FontCascade& other)
: m_fontDescription(other.m_fontDescription)
, m_fonts(other.m_fonts)
, m_letterSpacing(other.m_letterSpacing)
, m_wordSpacing(other.m_wordSpacing)
, m_useBackslashAsYenSymbol(other.m_useBackslashAsYenSymbol)
, m_enableKerning(computeEnableKerning())
, m_requiresShaping(computeRequiresShaping())
{
}
FontCascade& FontCascade::operator=(const FontCascade& other)
{
m_fontDescription = other.m_fontDescription;
m_fonts = other.m_fonts;
m_letterSpacing = other.m_letterSpacing;
m_wordSpacing = other.m_wordSpacing;
m_useBackslashAsYenSymbol = other.m_useBackslashAsYenSymbol;
m_enableKerning = other.m_enableKerning;
m_requiresShaping = other.m_requiresShaping;
return *this;
}
bool FontCascade::operator==(const FontCascade& other) const
{
if (isLoadingCustomFonts() || other.isLoadingCustomFonts())
return false;
if (m_fontDescription != other.m_fontDescription || m_letterSpacing != other.m_letterSpacing || m_wordSpacing != other.m_wordSpacing)
return false;
if (m_fonts == other.m_fonts)
return true;
if (!m_fonts || !other.m_fonts)
return false;
if (m_fonts->fontSelector() != other.m_fonts->fontSelector())
return false;
if (m_fonts->fontSelectorVersion() != other.m_fonts->fontSelectorVersion())
return false;
if (m_fonts->generation() != other.m_fonts->generation())
return false;
return true;
}
struct FontCascadeCacheKey {
FontDescriptionKey fontDescriptionKey; Vector<AtomString, 3> families;
unsigned fontSelectorId;
unsigned fontSelectorVersion;
};
struct FontCascadeCacheEntry {
WTF_MAKE_FAST_ALLOCATED;
public:
FontCascadeCacheEntry(FontCascadeCacheKey&& key, Ref<FontCascadeFonts>&& fonts)
: key(WTFMove(key))
, fonts(WTFMove(fonts))
{ }
FontCascadeCacheKey key;
Ref<FontCascadeFonts> fonts;
};
typedef HashMap<unsigned, std::unique_ptr<FontCascadeCacheEntry>, AlreadyHashed> FontCascadeCache;
static bool keysMatch(const FontCascadeCacheKey& a, const FontCascadeCacheKey& b)
{
if (a.fontDescriptionKey != b.fontDescriptionKey)
return false;
if (a.fontSelectorId != b.fontSelectorId || a.fontSelectorVersion != b.fontSelectorVersion)
return false;
unsigned size = a.families.size();
if (size != b.families.size())
return false;
for (unsigned i = 0; i < size; ++i) {
if (!FontCascadeDescription::familyNamesAreEqual(a.families[i], b.families[i]))
return false;
}
return true;
}
static FontCascadeCache& fontCascadeCache()
{
static NeverDestroyed<FontCascadeCache> cache;
return cache.get();
}
void invalidateFontCascadeCache()
{
fontCascadeCache().clear();
}
void clearWidthCaches()
{
for (auto& value : fontCascadeCache().values())
value->fonts.get().widthCache().clear();
}
static FontCascadeCacheKey makeFontCascadeCacheKey(const FontCascadeDescription& description, FontSelector* fontSelector)
{
FontCascadeCacheKey key;
key.fontDescriptionKey = FontDescriptionKey(description);
unsigned familyCount = description.familyCount();
key.families.reserveInitialCapacity(familyCount);
for (unsigned i = 0; i < familyCount; ++i)
key.families.uncheckedAppend(description.familyAt(i));
key.fontSelectorId = fontSelector ? fontSelector->uniqueId() : 0;
key.fontSelectorVersion = fontSelector ? fontSelector->version() : 0;
return key;
}
static unsigned computeFontCascadeCacheHash(const FontCascadeCacheKey& key)
{
IntegerHasher hasher;
hasher.add(key.fontDescriptionKey.computeHash());
hasher.add(key.fontSelectorId);
hasher.add(key.fontSelectorVersion);
for (unsigned i = 0; i < key.families.size(); ++i) {
auto& family = key.families[i];
hasher.add(family.isNull() ? 0 : FontCascadeDescription::familyNameHash(family));
}
return hasher.hash();
}
void pruneUnreferencedEntriesFromFontCascadeCache()
{
fontCascadeCache().removeIf([](auto& entry) {
return entry.value->fonts.get().hasOneRef();
});
}
void pruneSystemFallbackFonts()
{
for (auto& entry : fontCascadeCache().values())
entry->fonts->pruneSystemFallbacks();
}
static Ref<FontCascadeFonts> retrieveOrAddCachedFonts(const FontCascadeDescription& fontDescription, RefPtr<FontSelector>&& fontSelector)
{
auto key = makeFontCascadeCacheKey(fontDescription, fontSelector.get());
unsigned hash = computeFontCascadeCacheHash(key);
auto addResult = fontCascadeCache().add(hash, nullptr);
if (!addResult.isNewEntry && keysMatch(addResult.iterator->value->key, key))
return addResult.iterator->value->fonts.get();
auto& newEntry = addResult.iterator->value;
newEntry = makeUnique<FontCascadeCacheEntry>(WTFMove(key), FontCascadeFonts::create(WTFMove(fontSelector)));
Ref<FontCascadeFonts> glyphs = newEntry->fonts.get();
static const unsigned unreferencedPruneInterval = 50;
static const int maximumEntries = 400;
static unsigned pruneCounter;
if (!(++pruneCounter % unreferencedPruneInterval))
pruneUnreferencedEntriesFromFontCascadeCache();
if (fontCascadeCache().size() > maximumEntries)
fontCascadeCache().remove(fontCascadeCache().random());
return glyphs;
}
bool FontCascade::isCurrent(const FontSelector& fontSelector) const
{
if (!m_fonts)
return false;
if (m_fonts->generation() != FontCache::singleton().generation())
return false;
if (m_fonts->fontSelectorVersion() != fontSelector.version())
return false;
return true;
}
void FontCascade::update(RefPtr<FontSelector>&& fontSelector) const
{
m_fonts = retrieveOrAddCachedFonts(m_fontDescription, WTFMove(fontSelector));
m_useBackslashAsYenSymbol = useBackslashAsYenSignForFamily(firstFamily());
m_enableKerning = computeEnableKerning();
m_requiresShaping = computeRequiresShaping();
}
GlyphBuffer FontCascade::layoutText(CodePath codePathToUse, const TextRun& run, unsigned from, unsigned to, ForTextEmphasisOrNot forTextEmphasis) const
{
if (codePathToUse != Complex)
return layoutSimpleText(run, from, to, forTextEmphasis);
return layoutComplexText(run, from, to, forTextEmphasis);
}
FloatSize FontCascade::drawText(GraphicsContext& context, const TextRun& run, const FloatPoint& point, unsigned from, Optional<unsigned> to, CustomFontNotReadyAction customFontNotReadyAction) const
{
unsigned destination = to.valueOr(run.length());
auto glyphBuffer = layoutText(codePath(run, from, to), run, from, destination);
glyphBuffer.flatten();
if (glyphBuffer.isEmpty())
return FloatSize();
FloatPoint startPoint = point + FloatSize(glyphBuffer.initialAdvance());
drawGlyphBuffer(context, glyphBuffer, startPoint, customFontNotReadyAction);
return startPoint - point;
}
void FontCascade::drawEmphasisMarks(GraphicsContext& context, const TextRun& run, const AtomString& mark, const FloatPoint& point, unsigned from, Optional<unsigned> to) const
{
if (isLoadingCustomFonts())
return;
unsigned destination = to.valueOr(run.length());
auto glyphBuffer = layoutText(codePath(run, from, to), run, from, destination, ForTextEmphasisOrNot::ForTextEmphasis);
glyphBuffer.flatten();
if (glyphBuffer.isEmpty())
return;
FloatPoint startPoint = point + FloatSize(glyphBuffer.initialAdvance());
drawEmphasisMarks(context, glyphBuffer, mark, startPoint);
}
std::unique_ptr<DisplayList::DisplayList> FontCascade::displayListForTextRun(GraphicsContext& context, const TextRun& run, unsigned from, Optional<unsigned> to, CustomFontNotReadyAction customFontNotReadyAction) const
{
ASSERT(!context.paintingDisabled());
unsigned destination = to.valueOr(run.length());
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex && (enableKerning() || requiresShaping()) && (from || destination != run.length()))
codePathToUse = Complex;
auto glyphBuffer = layoutText(codePathToUse, run, from, destination);
glyphBuffer.flatten();
if (glyphBuffer.isEmpty())
return nullptr;
std::unique_ptr<DisplayList::DisplayList> displayList = makeUnique<DisplayList::DisplayList>();
GraphicsContext recordingContext([&](GraphicsContext& displayListContext) {
return makeUnique<DisplayList::Recorder>(displayListContext, *displayList, context.state(), FloatRect(), AffineTransform());
});
FloatPoint startPoint = toFloatPoint(FloatSize(glyphBuffer.initialAdvance()));
drawGlyphBuffer(recordingContext, glyphBuffer, startPoint, customFontNotReadyAction);
return displayList;
}
float FontCascade::widthOfTextRange(const TextRun& run, unsigned from, unsigned to, HashSet<const Font*>* fallbackFonts, float* outWidthBeforeRange, float* outWidthAfterRange) const
{
ASSERT(from <= to);
ASSERT(to <= run.length());
if (!run.length())
return 0;
float offsetBeforeRange = 0;
float offsetAfterRange = 0;
float totalWidth = 0;
auto codePathToUse = codePath(run);
if (codePathToUse == Complex) {
ComplexTextController complexIterator(*this, run, false, fallbackFonts);
complexIterator.advance(from, nullptr, IncludePartialGlyphs, fallbackFonts);
offsetBeforeRange = complexIterator.runWidthSoFar();
complexIterator.advance(to, nullptr, IncludePartialGlyphs, fallbackFonts);
offsetAfterRange = complexIterator.runWidthSoFar();
complexIterator.advance(run.length(), nullptr, IncludePartialGlyphs, fallbackFonts);
totalWidth = complexIterator.runWidthSoFar();
} else {
WidthIterator simpleIterator(*this, run, fallbackFonts);
GlyphBuffer glyphBuffer;
simpleIterator.advance(from, glyphBuffer);
offsetBeforeRange = simpleIterator.runWidthSoFar();
simpleIterator.advance(to, glyphBuffer);
offsetAfterRange = simpleIterator.runWidthSoFar();
simpleIterator.advance(run.length(), glyphBuffer);
totalWidth = simpleIterator.runWidthSoFar();
simpleIterator.finalize(glyphBuffer);
}
if (outWidthBeforeRange)
*outWidthBeforeRange = offsetBeforeRange;
if (outWidthAfterRange)
*outWidthAfterRange = totalWidth - offsetAfterRange;
return offsetAfterRange - offsetBeforeRange;
}
float FontCascade::width(const TextRun& run, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
{
if (!run.length())
return 0;
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex) {
if (!canReturnFallbackFontsForComplexText())
fallbackFonts = nullptr;
if (codePathToUse != SimpleWithGlyphOverflow && (glyphOverflow && !glyphOverflow->computeBounds))
glyphOverflow = nullptr;
}
bool hasWordSpacingOrLetterSpacing = wordSpacing() || letterSpacing();
float* cacheEntry = m_fonts->widthCache().add(run, std::numeric_limits<float>::quiet_NaN(), enableKerning() || requiresShaping(), hasWordSpacingOrLetterSpacing, glyphOverflow);
if (cacheEntry && !std::isnan(*cacheEntry))
return *cacheEntry;
HashSet<const Font*> localFallbackFonts;
if (!fallbackFonts)
fallbackFonts = &localFallbackFonts;
float result;
if (codePathToUse == Complex)
result = floatWidthForComplexText(run, fallbackFonts, glyphOverflow);
else
result = floatWidthForSimpleText(run, fallbackFonts, glyphOverflow);
if (cacheEntry && fallbackFonts->isEmpty())
*cacheEntry = result;
return result;
}
float FontCascade::widthForSimpleText(StringView text, TextDirection textDirection) const
{
if (text.isNull() || text.isEmpty())
return 0;
ASSERT(codePath(TextRun(text)) != FontCascade::Complex);
float* cacheEntry = m_fonts->widthCache().add(text, std::numeric_limits<float>::quiet_NaN());
if (cacheEntry && !std::isnan(*cacheEntry))
return *cacheEntry;
GlyphBuffer glyphBuffer;
float runWidth = 0;
auto& font = primaryFont();
for (unsigned i = 0; i < text.length(); ++i) {
auto glyph = glyphDataForCharacter(text[i], false).glyph;
auto glyphWidth = font.widthForGlyph(glyph);
runWidth += glyphWidth;
glyphBuffer.add(glyph, font, glyphWidth, i);
}
font.applyTransforms(glyphBuffer, 0, 0, enableKerning(), requiresShaping(), fontDescription().computedLocale(), text, textDirection);
float runWidthDifferenceWithTransformApplied = -runWidth;
for (size_t i = 0; i < glyphBuffer.size(); ++i)
runWidthDifferenceWithTransformApplied += glyphBuffer.advanceAt(i).width();
runWidth += runWidthDifferenceWithTransformApplied;
if (cacheEntry)
*cacheEntry = runWidth;
return runWidth;
}
GlyphData FontCascade::glyphDataForCharacter(UChar32 c, bool mirror, FontVariant variant) const
{
if (variant == AutoVariant) {
if (m_fontDescription.variantCaps() == FontVariantCaps::Small) {
UChar32 upperC = u_toupper(c);
if (upperC != c) {
c = upperC;
variant = SmallCapsVariant;
} else
variant = NormalVariant;
} else
variant = NormalVariant;
}
if (mirror)
c = u_charMirror(c);
return m_fonts->glyphDataForCharacter(c, m_fontDescription, variant);
}
bool FontCascade::hasValidAverageCharWidth() const
{
const AtomString& family = firstFamily();
if (family.isEmpty())
return false;
#if PLATFORM(COCOA)
if (primaryFontIsSystemFont())
return false;
#endif
static const auto map = makeNeverDestroyed(HashSet<AtomString> {
"American Typewriter",
"Arial Hebrew",
"Chalkboard",
"Cochin",
"Corsiva Hebrew",
"Courier",
"Euphemia UCAS",
"Geneva",
"Gill Sans",
"Hei",
"Helvetica",
"Hoefler Text",
"InaiMathi",
"Kai",
"Lucida Grande",
"Marker Felt",
"Monaco",
"Mshtakan",
"New Peninim MT",
"Osaka",
"Raanana",
"STHeiti",
"Symbol",
"Times",
"Apple Braille",
"Apple LiGothic",
"Apple LiSung",
"Apple Symbols",
"AppleGothic",
"AppleMyungjo",
"#GungSeo",
"#HeadLineA",
"#PCMyungjo",
"#PilGi",
});
return !map.get().contains(family);
}
bool FontCascade::fastAverageCharWidthIfAvailable(float& width) const
{
bool success = hasValidAverageCharWidth();
if (success)
width = roundf(primaryFont().avgCharWidth()); return success;
}
void FontCascade::adjustSelectionRectForText(const TextRun& run, LayoutRect& selectionRect, unsigned from, Optional<unsigned> to) const
{
unsigned destination = to.valueOr(run.length());
if (codePath(run, from, to) != Complex)
return adjustSelectionRectForSimpleText(run, selectionRect, from, destination);
return adjustSelectionRectForComplexText(run, selectionRect, from, destination);
}
int FontCascade::offsetForPosition(const TextRun& run, float x, bool includePartialGlyphs) const
{
if (codePath(run, x) != Complex)
return offsetForPositionForSimpleText(run, x, includePartialGlyphs);
return offsetForPositionForComplexText(run, x, includePartialGlyphs);
}
template <typename CharacterType>
static inline String normalizeSpacesInternal(const CharacterType* characters, unsigned length)
{
StringBuilder normalized;
normalized.reserveCapacity(length);
for (unsigned i = 0; i < length; ++i)
normalized.append(FontCascade::normalizeSpaces(characters[i]));
return normalized.toString();
}
String FontCascade::normalizeSpaces(const LChar* characters, unsigned length)
{
return normalizeSpacesInternal(characters, length);
}
String FontCascade::normalizeSpaces(const UChar* characters, unsigned length)
{
return normalizeSpacesInternal(characters, length);
}
static bool shouldUseFontSmoothing = true;
void FontCascade::setShouldUseSmoothing(bool shouldUseSmoothing)
{
ASSERT(isMainThread());
shouldUseFontSmoothing = shouldUseSmoothing;
}
bool FontCascade::shouldUseSmoothing()
{
return shouldUseFontSmoothing;
}
#if !PLATFORM(COCOA)
bool FontCascade::isSubpixelAntialiasingAvailable()
{
return false;
}
#endif
void FontCascade::setCodePath(CodePath p)
{
s_codePath = p;
}
FontCascade::CodePath FontCascade::codePath()
{
return s_codePath;
}
FontCascade::CodePath FontCascade::codePath(const TextRun& run, Optional<unsigned> from, Optional<unsigned> to) const
{
if (s_codePath != Auto)
return s_codePath;
#if !USE(FREETYPE)
if ((enableKerning() || requiresShaping()) && (from.valueOr(0) || to.valueOr(run.length()) != run.length()))
return Complex;
#else
UNUSED_PARAM(from);
UNUSED_PARAM(to);
#endif
#if PLATFORM(COCOA) || USE(FREETYPE)
if (m_fontDescription.featureSettings().size() > 0 || !m_fontDescription.variantSettings().isAllNormal())
return Complex;
#else
if (run.length() > 1 && (enableKerning() || requiresShaping()))
return Complex;
#endif
if (!run.characterScanForCodePath())
return Simple;
if (run.is8Bit())
return Simple;
return characterRangeCodePath(run.characters16(), run.length());
}
FontCascade::CodePath FontCascade::characterRangeCodePath(const UChar* characters, unsigned len)
{
CodePath result = Simple;
bool previousCharacterIsEmojiGroupCandidate = false;
for (unsigned i = 0; i < len; i++) {
const UChar c = characters[i];
if (c == zeroWidthJoiner && previousCharacterIsEmojiGroupCandidate)
return Complex;
previousCharacterIsEmojiGroupCandidate = false;
if (c < 0x2E5) continue;
if (c <= 0x2E9)
return Complex;
if (c < 0x300) continue;
if (c <= 0x36F)
return Complex;
if (c < 0x0591 || c == 0x05BE) continue;
if (c <= 0x05CF)
return Complex;
if (c < 0x0600)
continue;
if (c <= 0x109F)
return Complex;
if (c < 0x1100)
continue;
if (c <= 0x11FF)
return Complex;
if (c < 0x135D) continue;
if (c <= 0x135F)
return Complex;
if (c < 0x1700) continue;
if (c <= 0x18AF)
return Complex;
if (c < 0x1900) continue;
if (c <= 0x194F)
return Complex;
if (c < 0x1980) continue;
if (c <= 0x19DF)
return Complex;
if (c < 0x1A00) continue;
if (c <= 0x1CFF)
return Complex;
if (c < 0x1DC0) continue;
if (c <= 0x1DFF)
return Complex;
if (c <= 0x2000) {
result = SimpleWithGlyphOverflow;
continue;
}
if (c < 0x20D0) continue;
if (c <= 0x20FF)
return Complex;
if (c < 0x26F9)
continue;
if (c < 0x26FA)
return Complex;
if (c < 0x2CEF) continue;
if (c <= 0x2CF1)
return Complex;
if (c < 0x302A) continue;
if (c <= 0x302F)
return Complex;
if (c < 0x3099)
continue;
if (c < 0x309D)
return Complex;
if (c < 0xA67C) continue;
if (c <= 0xA67D)
return Complex;
if (c < 0xA6F0) continue;
if (c <= 0xA6F1)
return Complex;
if (c < 0xA800)
continue;
if (c <= 0xABFF)
return Complex;
if (c < 0xD7B0) continue;
if (c <= 0xD7FF)
return Complex;
if (c <= 0xDBFF) {
if (i == len - 1)
continue;
UChar next = characters[++i];
if (!U16_IS_TRAIL(next))
continue;
UChar32 supplementaryCharacter = U16_GET_SUPPLEMENTARY(c, next);
if (supplementaryCharacter < 0x10A00)
continue;
if (supplementaryCharacter < 0x10A60) return Complex;
if (supplementaryCharacter < 0x11000)
continue;
if (supplementaryCharacter < 0x11080) return Complex;
if (supplementaryCharacter < 0x110D0) return Complex;
if (supplementaryCharacter < 0x11100)
continue;
if (supplementaryCharacter < 0x11150) return Complex;
if (supplementaryCharacter < 0x11180) return Complex;
if (supplementaryCharacter < 0x111E0) return Complex;
if (supplementaryCharacter < 0x11200)
continue;
if (supplementaryCharacter < 0x11250) return Complex;
if (supplementaryCharacter < 0x112B0)
continue;
if (supplementaryCharacter < 0x11300) return Complex;
if (supplementaryCharacter < 0x11380) return Complex;
if (supplementaryCharacter < 0x11400)
continue;
if (supplementaryCharacter < 0x11480) return Complex;
if (supplementaryCharacter < 0x114E0) return Complex;
if (supplementaryCharacter < 0x11580)
continue;
if (supplementaryCharacter < 0x11600) return Complex;
if (supplementaryCharacter < 0x11660) return Complex;
if (supplementaryCharacter < 0x11680)
continue;
if (supplementaryCharacter < 0x116D0) return Complex;
if (supplementaryCharacter < 0x11740)
continue;
if (supplementaryCharacter < 0x11C00) return Complex;
if (supplementaryCharacter < 0x11C70) return Complex;
if (supplementaryCharacter < 0x11CC0) return Complex;
if (supplementaryCharacter < 0x1E900)
continue;
if (supplementaryCharacter < 0x1E960) return Complex;
if (supplementaryCharacter < 0x1F1E6) continue;
if (supplementaryCharacter <= 0x1F1FF)
return Complex;
if (isEmojiFitzpatrickModifier(supplementaryCharacter))
return Complex;
if (isEmojiGroupCandidate(supplementaryCharacter)) {
previousCharacterIsEmojiGroupCandidate = true;
continue;
}
if (supplementaryCharacter < 0xE0000)
continue;
if (supplementaryCharacter < 0xE0080) return Complex;
if (supplementaryCharacter < 0xE0100) continue;
if (supplementaryCharacter <= 0xE01EF)
return Complex;
continue;
}
if (c < 0xFE00) continue;
if (c <= 0xFE0F)
return Complex;
if (c < 0xFE20) continue;
if (c <= 0xFE2F)
return Complex;
}
return result;
}
bool FontCascade::isCJKIdeograph(UChar32 c)
{
if (c >= 0x4E00 && c <= 0x9FFF)
return true;
if (c >= 0x3400 && c <= 0x4DBF)
return true;
if (c >= 0x2E80 && c <= 0x2EFF)
return true;
if (c >= 0x2F00 && c <= 0x2FDF)
return true;
if (c >= 0x31C0 && c <= 0x31EF)
return true;
if (c >= 0xF900 && c <= 0xFAFF)
return true;
if (c >= 0x20000 && c <= 0x2A6DF)
return true;
if (c >= 0x2A700 && c <= 0x2B73F)
return true;
if (c >= 0x2B740 && c <= 0x2B81F)
return true;
if (c >= 0x2F800 && c <= 0x2FA1F)
return true;
return false;
}
bool FontCascade::isCJKIdeographOrSymbol(UChar32 c)
{
if ((c == 0x2C7) || (c == 0x2CA) || (c == 0x2CB) || (c == 0x2D9))
return true;
if ((c == 0x2020) || (c == 0x2021) || (c == 0x2030) || (c == 0x203B) || (c == 0x203C)
|| (c == 0x2042) || (c == 0x2047) || (c == 0x2048) || (c == 0x2049) || (c == 0x2051)
|| (c == 0x20DD) || (c == 0x20DE) || (c == 0x2100) || (c == 0x2103) || (c == 0x2105)
|| (c == 0x2109) || (c == 0x210A) || (c == 0x2113) || (c == 0x2116) || (c == 0x2121)
|| (c == 0x212B) || (c == 0x213B) || (c == 0x2150) || (c == 0x2151) || (c == 0x2152))
return true;
if (c >= 0x2156 && c <= 0x215A)
return true;
if (c >= 0x2160 && c <= 0x216B)
return true;
if (c >= 0x2170 && c <= 0x217B)
return true;
if ((c == 0x217F) || (c == 0x2189) || (c == 0x2307) || (c == 0x2312) || (c == 0x23BE) || (c == 0x23BF))
return true;
if (c >= 0x23C0 && c <= 0x23CC)
return true;
if ((c == 0x23CE) || (c == 0x2423))
return true;
if (c >= 0x2460 && c <= 0x2492)
return true;
if (c >= 0x249C && c <= 0x24FF)
return true;
if ((c == 0x25A0) || (c == 0x25A1) || (c == 0x25A2) || (c == 0x25AA) || (c == 0x25AB))
return true;
if ((c == 0x25B1) || (c == 0x25B2) || (c == 0x25B3) || (c == 0x25B6) || (c == 0x25B7) || (c == 0x25BC) || (c == 0x25BD))
return true;
if ((c == 0x25C0) || (c == 0x25C1) || (c == 0x25C6) || (c == 0x25C7) || (c == 0x25C9) || (c == 0x25CB) || (c == 0x25CC))
return true;
if (c >= 0x25CE && c <= 0x25D3)
return true;
if (c >= 0x25E2 && c <= 0x25E6)
return true;
if (c == 0x25EF)
return true;
if (c >= 0x2600 && c <= 0x2603)
return true;
if ((c == 0x2605) || (c == 0x2606) || (c == 0x260E) || (c == 0x2616) || (c == 0x2617) || (c == 0x2640) || (c == 0x2642))
return true;
if (c >= 0x2660 && c <= 0x266F)
return true;
if (c >= 0x2672 && c <= 0x267D)
return true;
if ((c == 0x26A0) || (c == 0x26BD) || (c == 0x26BE) || (c == 0x2713) || (c == 0x271A) || (c == 0x273F) || (c == 0x2740) || (c == 0x2756))
return true;
if (c >= 0x2776 && c <= 0x277F)
return true;
if (c == 0x2B1A)
return true;
if (c >= 0x2FF0 && c <= 0x2FFF)
return true;
if (c >= 0x3000 && c < 0x3030)
return true;
if (c > 0x3030 && c <= 0x303F)
return true;
if (c >= 0x3040 && c <= 0x309F)
return true;
if (c >= 0x30A0 && c <= 0x30FF)
return true;
if (c >= 0x3100 && c <= 0x312F)
return true;
if (c >= 0x3190 && c <= 0x319F)
return true;
if (c >= 0x31A0 && c <= 0x31BF)
return true;
if (c >= 0x3200 && c <= 0x32FF)
return true;
if (c >= 0x3300 && c <= 0x33FF)
return true;
if (c >= 0xF860 && c <= 0xF862)
return true;
if (c >= 0xFE30 && c <= 0xFE4F)
return true;
if ((c == 0xFE10) || (c == 0xFE11) || (c == 0xFE12) || (c == 0xFE19))
return true;
if ((c == 0xFF0D) || (c == 0xFF1B) || (c == 0xFF1C) || (c == 0xFF1E))
return false;
if (c >= 0xFF00 && c <= 0xFFEF)
return true;
if (c == 0x1F100)
return true;
if (c >= 0x1F110 && c <= 0x1F129)
return true;
if (c >= 0x1F130 && c <= 0x1F149)
return true;
if (c >= 0x1F150 && c <= 0x1F169)
return true;
if (c >= 0x1F170 && c <= 0x1F189)
return true;
if (c >= 0x1F200 && c <= 0x1F6C5)
return true;
return isCJKIdeograph(c);
}
std::pair<unsigned, bool> FontCascade::expansionOpportunityCountInternal(const LChar* characters, unsigned length, TextDirection direction, ExpansionBehavior expansionBehavior)
{
unsigned count = 0;
bool isAfterExpansion = (expansionBehavior & LeftExpansionMask) == ForbidLeftExpansion;
if ((expansionBehavior & LeftExpansionMask) == ForceLeftExpansion) {
++count;
isAfterExpansion = true;
}
if (direction == TextDirection::LTR) {
for (unsigned i = 0; i < length; ++i) {
if (treatAsSpace(characters[i])) {
count++;
isAfterExpansion = true;
} else
isAfterExpansion = false;
}
} else {
for (unsigned i = length; i > 0; --i) {
if (treatAsSpace(characters[i - 1])) {
count++;
isAfterExpansion = true;
} else
isAfterExpansion = false;
}
}
if (!isAfterExpansion && (expansionBehavior & RightExpansionMask) == ForceRightExpansion) {
++count;
isAfterExpansion = true;
} else if (isAfterExpansion && (expansionBehavior & RightExpansionMask) == ForbidRightExpansion) {
ASSERT(count);
--count;
isAfterExpansion = false;
}
return std::make_pair(count, isAfterExpansion);
}
std::pair<unsigned, bool> FontCascade::expansionOpportunityCountInternal(const UChar* characters, unsigned length, TextDirection direction, ExpansionBehavior expansionBehavior)
{
static bool expandAroundIdeographs = canExpandAroundIdeographsInComplexText();
unsigned count = 0;
bool isAfterExpansion = (expansionBehavior & LeftExpansionMask) == ForbidLeftExpansion;
if ((expansionBehavior & LeftExpansionMask) == ForceLeftExpansion) {
++count;
isAfterExpansion = true;
}
if (direction == TextDirection::LTR) {
for (unsigned i = 0; i < length; ++i) {
UChar32 character = characters[i];
if (treatAsSpace(character)) {
count++;
isAfterExpansion = true;
continue;
}
if (U16_IS_LEAD(character) && i + 1 < length && U16_IS_TRAIL(characters[i + 1])) {
character = U16_GET_SUPPLEMENTARY(character, characters[i + 1]);
i++;
}
if (expandAroundIdeographs && isCJKIdeographOrSymbol(character)) {
if (!isAfterExpansion)
count++;
count++;
isAfterExpansion = true;
continue;
}
isAfterExpansion = false;
}
} else {
for (unsigned i = length; i > 0; --i) {
UChar32 character = characters[i - 1];
if (treatAsSpace(character)) {
count++;
isAfterExpansion = true;
continue;
}
if (U16_IS_TRAIL(character) && i > 1 && U16_IS_LEAD(characters[i - 2])) {
character = U16_GET_SUPPLEMENTARY(characters[i - 2], character);
i--;
}
if (expandAroundIdeographs && isCJKIdeographOrSymbol(character)) {
if (!isAfterExpansion)
count++;
count++;
isAfterExpansion = true;
continue;
}
isAfterExpansion = false;
}
}
if (!isAfterExpansion && (expansionBehavior & RightExpansionMask) == ForceRightExpansion) {
++count;
isAfterExpansion = true;
} else if (isAfterExpansion && (expansionBehavior & RightExpansionMask) == ForbidRightExpansion) {
ASSERT(count);
--count;
isAfterExpansion = false;
}
return std::make_pair(count, isAfterExpansion);
}
std::pair<unsigned, bool> FontCascade::expansionOpportunityCount(const StringView& stringView, TextDirection direction, ExpansionBehavior expansionBehavior)
{
if (stringView.is8Bit())
return expansionOpportunityCountInternal(stringView.characters8(), stringView.length(), direction, expansionBehavior);
return expansionOpportunityCountInternal(stringView.characters16(), stringView.length(), direction, expansionBehavior);
}
bool FontCascade::leftExpansionOpportunity(const StringView& stringView, TextDirection direction)
{
if (!stringView.length())
return false;
UChar32 initialCharacter;
if (direction == TextDirection::LTR) {
initialCharacter = stringView[0];
if (U16_IS_LEAD(initialCharacter) && stringView.length() > 1 && U16_IS_TRAIL(stringView[1]))
initialCharacter = U16_GET_SUPPLEMENTARY(initialCharacter, stringView[1]);
} else {
initialCharacter = stringView[stringView.length() - 1];
if (U16_IS_TRAIL(initialCharacter) && stringView.length() > 1 && U16_IS_LEAD(stringView[stringView.length() - 2]))
initialCharacter = U16_GET_SUPPLEMENTARY(stringView[stringView.length() - 2], initialCharacter);
}
return canExpandAroundIdeographsInComplexText() && isCJKIdeographOrSymbol(initialCharacter);
}
bool FontCascade::rightExpansionOpportunity(const StringView& stringView, TextDirection direction)
{
if (!stringView.length())
return false;
UChar32 finalCharacter;
if (direction == TextDirection::LTR) {
finalCharacter = stringView[stringView.length() - 1];
if (U16_IS_TRAIL(finalCharacter) && stringView.length() > 1 && U16_IS_LEAD(stringView[stringView.length() - 2]))
finalCharacter = U16_GET_SUPPLEMENTARY(stringView[stringView.length() - 2], finalCharacter);
} else {
finalCharacter = stringView[0];
if (U16_IS_LEAD(finalCharacter) && stringView.length() > 1 && U16_IS_TRAIL(stringView[1]))
finalCharacter = U16_GET_SUPPLEMENTARY(finalCharacter, stringView[1]);
}
return treatAsSpace(finalCharacter) || (canExpandAroundIdeographsInComplexText() && isCJKIdeographOrSymbol(finalCharacter));
}
bool FontCascade::canReceiveTextEmphasis(UChar32 c)
{
if (U_GET_GC_MASK(c) & (U_GC_Z_MASK | U_GC_CN_MASK | U_GC_CC_MASK | U_GC_CF_MASK))
return false;
if (c == ethiopicWordspace || c == aegeanWordSeparatorLine || c == aegeanWordSeparatorDot
|| c == ugariticWordDivider || c == tibetanMarkIntersyllabicTsheg || c == tibetanMarkDelimiterTshegBstar)
return false;
return true;
}
bool FontCascade::isLoadingCustomFonts() const
{
return m_fonts && m_fonts->isLoadingCustomFonts();
}
enum class GlyphUnderlineType : uint8_t {
SkipDescenders,
SkipGlyph,
DrawOverGlyph
};
static GlyphUnderlineType computeUnderlineType(const TextRun& textRun, const GlyphBuffer& glyphBuffer, unsigned index)
{
UChar32 baseCharacter;
auto offsetInString = glyphBuffer.stringOffsetAt(index);
GlyphBufferStringOffset textRunLength = textRun.length();
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(offsetInString < textRunLength);
if (textRun.is8Bit())
baseCharacter = textRun.characters8()[offsetInString];
else
U16_GET(textRun.characters16(), 0, offsetInString, textRunLength, baseCharacter);
UBlockCode blockCode = ublock_getCode(baseCharacter);
switch (blockCode) {
case UBLOCK_CJK_RADICALS_SUPPLEMENT:
case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION:
case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS:
case UBLOCK_CJK_COMPATIBILITY:
case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A:
case UBLOCK_CJK_UNIFIED_IDEOGRAPHS:
case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS:
case UBLOCK_CJK_COMPATIBILITY_FORMS:
case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B:
case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT:
case UBLOCK_CJK_STROKES:
case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C:
case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D:
case UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS:
case UBLOCK_LINEAR_B_IDEOGRAMS:
case UBLOCK_ENCLOSED_IDEOGRAPHIC_SUPPLEMENT:
case UBLOCK_HIRAGANA:
case UBLOCK_KATAKANA:
case UBLOCK_BOPOMOFO:
case UBLOCK_BOPOMOFO_EXTENDED:
case UBLOCK_HANGUL_JAMO:
case UBLOCK_HANGUL_COMPATIBILITY_JAMO:
case UBLOCK_HANGUL_SYLLABLES:
case UBLOCK_HANGUL_JAMO_EXTENDED_A:
case UBLOCK_HANGUL_JAMO_EXTENDED_B:
return GlyphUnderlineType::DrawOverGlyph;
default:
return GlyphUnderlineType::SkipDescenders;
}
}
Optional<GlyphData> FontCascade::getEmphasisMarkGlyphData(const AtomString& mark) const
{
if (mark.isEmpty())
return WTF::nullopt;
UChar32 character;
if (!mark.is8Bit()) {
SurrogatePairAwareTextIterator iterator(mark.characters16(), 0, mark.length(), mark.length());
unsigned clusterLength;
if (!iterator.consume(character, clusterLength))
return WTF::nullopt;
} else
character = mark[0];
Optional<GlyphData> glyphData(glyphDataForCharacter(character, false, EmphasisMarkVariant));
return glyphData.value().isValid() ? glyphData : WTF::nullopt;
}
int FontCascade::emphasisMarkAscent(const AtomString& mark) const
{
Optional<GlyphData> markGlyphData = getEmphasisMarkGlyphData(mark);
if (!markGlyphData)
return 0;
const Font* markFontData = markGlyphData.value().font;
ASSERT(markFontData);
if (!markFontData)
return 0;
return markFontData->fontMetrics().ascent();
}
int FontCascade::emphasisMarkDescent(const AtomString& mark) const
{
Optional<GlyphData> markGlyphData = getEmphasisMarkGlyphData(mark);
if (!markGlyphData)
return 0;
const Font* markFontData = markGlyphData.value().font;
ASSERT(markFontData);
if (!markFontData)
return 0;
return markFontData->fontMetrics().descent();
}
int FontCascade::emphasisMarkHeight(const AtomString& mark) const
{
Optional<GlyphData> markGlyphData = getEmphasisMarkGlyphData(mark);
if (!markGlyphData)
return 0;
const Font* markFontData = markGlyphData.value().font;
ASSERT(markFontData);
if (!markFontData)
return 0;
return markFontData->fontMetrics().height();
}
GlyphBuffer FontCascade::layoutSimpleText(const TextRun& run, unsigned from, unsigned to, ForTextEmphasisOrNot forTextEmphasis) const
{
GlyphBuffer glyphBuffer;
WidthIterator it(*this, run, 0, false, forTextEmphasis);
GlyphBuffer localGlyphBuffer;
it.advance(from, localGlyphBuffer);
float beforeWidth = it.runWidthSoFar();
it.advance(to, glyphBuffer);
if (glyphBuffer.isEmpty())
return glyphBuffer;
float afterWidth = it.runWidthSoFar();
float initialAdvance = 0;
if (run.rtl()) {
it.advance(run.length(), localGlyphBuffer);
it.finalize(localGlyphBuffer);
initialAdvance = it.runWidthSoFar() - afterWidth;
} else {
it.finalize(localGlyphBuffer);
initialAdvance = beforeWidth;
}
glyphBuffer.expandInitialAdvance(initialAdvance);
if (!glyphBuffer.isEmpty()) {
glyphBuffer.expandInitialAdvance(GlyphBufferAdvance(glyphBuffer.originAt(0).x(), glyphBuffer.originAt(0).y()));
}
if (run.rtl())
glyphBuffer.reverse(0, glyphBuffer.size());
return glyphBuffer;
}
GlyphBuffer FontCascade::layoutComplexText(const TextRun& run, unsigned from, unsigned to, ForTextEmphasisOrNot forTextEmphasis) const
{
GlyphBuffer glyphBuffer;
ComplexTextController controller(*this, run, false, 0, forTextEmphasis);
GlyphBuffer dummyGlyphBuffer;
controller.advance(from, &dummyGlyphBuffer);
controller.advance(to, &glyphBuffer);
if (glyphBuffer.isEmpty())
return glyphBuffer;
if (run.rtl()) {
FloatSize initialAdvance = controller.totalAdvance();
for (unsigned i = 0; i < dummyGlyphBuffer.size(); ++i)
initialAdvance -= toFloatSize(dummyGlyphBuffer.advanceAt(i));
for (unsigned i = 0; i < glyphBuffer.size(); ++i)
initialAdvance -= toFloatSize(glyphBuffer.advanceAt(i));
glyphBuffer.reverse(0, glyphBuffer.size());
glyphBuffer.setInitialAdvance(initialAdvance);
} else {
FloatSize initialAdvance = toFloatSize(dummyGlyphBuffer.initialAdvance());
for (unsigned i = 0; i < dummyGlyphBuffer.size(); ++i)
initialAdvance += toFloatSize(dummyGlyphBuffer.advanceAt(i));
glyphBuffer.setInitialAdvance(initialAdvance);
}
return glyphBuffer;
}
inline bool shouldDrawIfLoading(const Font& font, FontCascade::CustomFontNotReadyAction customFontNotReadyAction)
{
return !font.isInterstitial() || font.visibility() == Font::Visibility::Visible || customFontNotReadyAction == FontCascade::CustomFontNotReadyAction::UseFallbackIfFontNotReady;
}
void FontCascade::drawGlyphBuffer(GraphicsContext& context, const GlyphBuffer& glyphBuffer, FloatPoint& point, CustomFontNotReadyAction customFontNotReadyAction) const
{
ASSERT(glyphBuffer.isFlattened());
const Font* fontData = &glyphBuffer.fontAt(0);
FloatPoint startPoint = point;
float nextX = startPoint.x() + glyphBuffer.advanceAt(0).width();
float nextY = startPoint.y() + glyphBuffer.advanceAt(0).height();
unsigned lastFrom = 0;
unsigned nextGlyph = 1;
while (nextGlyph < glyphBuffer.size()) {
const Font& nextFontData = glyphBuffer.fontAt(nextGlyph);
if (&nextFontData != fontData) {
if (shouldDrawIfLoading(*fontData, customFontNotReadyAction))
context.drawGlyphs(*fontData, glyphBuffer, lastFrom, nextGlyph - lastFrom, startPoint, m_fontDescription.fontSmoothing());
lastFrom = nextGlyph;
fontData = &nextFontData;
startPoint.setX(nextX);
startPoint.setY(nextY);
}
nextX += glyphBuffer.advanceAt(nextGlyph).width();
nextY += glyphBuffer.advanceAt(nextGlyph).height();
nextGlyph++;
}
if (shouldDrawIfLoading(*fontData, customFontNotReadyAction))
context.drawGlyphs(*fontData, glyphBuffer, lastFrom, nextGlyph - lastFrom, startPoint, m_fontDescription.fontSmoothing());
point.setX(nextX);
}
inline static float offsetToMiddleOfGlyph(const Font& fontData, Glyph glyph)
{
if (fontData.platformData().orientation() == FontOrientation::Horizontal) {
FloatRect bounds = fontData.boundsForGlyph(glyph);
return bounds.x() + bounds.width() / 2;
}
return fontData.widthForGlyph(glyph) / 2;
}
inline static float offsetToMiddleOfGlyphAtIndex(const GlyphBuffer& glyphBuffer, unsigned i)
{
return offsetToMiddleOfGlyph(glyphBuffer.fontAt(i), glyphBuffer.glyphAt(i));
}
void FontCascade::drawEmphasisMarks(GraphicsContext& context, const GlyphBuffer& glyphBuffer, const AtomString& mark, const FloatPoint& point) const
{
ASSERT(glyphBuffer.isFlattened());
Optional<GlyphData> markGlyphData = getEmphasisMarkGlyphData(mark);
if (!markGlyphData)
return;
const Font* markFontData = markGlyphData.value().font;
ASSERT(markFontData);
if (!markFontData)
return;
Glyph markGlyph = markGlyphData.value().glyph;
Glyph spaceGlyph = markFontData->spaceGlyph();
float middleOfLastGlyph = offsetToMiddleOfGlyphAtIndex(glyphBuffer, 0);
FloatPoint startPoint(point.x() + middleOfLastGlyph - offsetToMiddleOfGlyph(*markFontData, markGlyph), point.y());
GlyphBuffer markBuffer;
for (unsigned i = 0; i + 1 < glyphBuffer.size(); ++i) {
float middleOfNextGlyph = offsetToMiddleOfGlyphAtIndex(glyphBuffer, i + 1);
float advance = glyphBuffer.advanceAt(i).width() - middleOfLastGlyph + middleOfNextGlyph;
markBuffer.add(glyphBuffer.glyphAt(i) ? markGlyph : spaceGlyph, *markFontData, advance);
middleOfLastGlyph = middleOfNextGlyph;
}
markBuffer.add(glyphBuffer.glyphAt(glyphBuffer.size() - 1) ? markGlyph : spaceGlyph, *markFontData, 0);
drawGlyphBuffer(context, markBuffer, startPoint, CustomFontNotReadyAction::DoNotPaintIfFontNotReady);
}
float FontCascade::floatWidthForSimpleText(const TextRun& run, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
{
WidthIterator it(*this, run, fallbackFonts, glyphOverflow);
GlyphBuffer glyphBuffer;
it.advance(run.length(), glyphBuffer);
it.finalize(glyphBuffer);
if (glyphOverflow) {
glyphOverflow->top = std::max<int>(glyphOverflow->top, ceilf(-it.minGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().ascent()));
glyphOverflow->bottom = std::max<int>(glyphOverflow->bottom, ceilf(it.maxGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().descent()));
glyphOverflow->left = ceilf(it.firstGlyphOverflow());
glyphOverflow->right = ceilf(it.lastGlyphOverflow());
}
return it.runWidthSoFar();
}
float FontCascade::floatWidthForComplexText(const TextRun& run, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
{
ComplexTextController controller(*this, run, true, fallbackFonts);
if (glyphOverflow) {
glyphOverflow->top = std::max<int>(glyphOverflow->top, ceilf(-controller.minGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().ascent()));
glyphOverflow->bottom = std::max<int>(glyphOverflow->bottom, ceilf(controller.maxGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().descent()));
glyphOverflow->left = std::max<int>(0, ceilf(-controller.minGlyphBoundingBoxX()));
glyphOverflow->right = std::max<int>(0, ceilf(controller.maxGlyphBoundingBoxX() - controller.totalAdvance().width()));
}
return controller.totalAdvance().width();
}
void FontCascade::adjustSelectionRectForSimpleText(const TextRun& run, LayoutRect& selectionRect, unsigned from, unsigned to) const
{
GlyphBuffer glyphBuffer;
WidthIterator it(*this, run);
it.advance(from, glyphBuffer);
float beforeWidth = it.runWidthSoFar();
it.advance(to, glyphBuffer);
float afterWidth = it.runWidthSoFar();
if (run.rtl()) {
it.advance(run.length(), glyphBuffer);
it.finalize(glyphBuffer);
float totalWidth = it.runWidthSoFar();
selectionRect.move(totalWidth - afterWidth, 0);
} else {
it.finalize(glyphBuffer);
selectionRect.move(beforeWidth, 0);
}
selectionRect.setWidth(LayoutUnit::fromFloatCeil(afterWidth - beforeWidth));
}
void FontCascade::adjustSelectionRectForComplexText(const TextRun& run, LayoutRect& selectionRect, unsigned from, unsigned to) const
{
ComplexTextController controller(*this, run);
controller.advance(from);
float beforeWidth = controller.runWidthSoFar();
controller.advance(to);
float afterWidth = controller.runWidthSoFar();
if (run.rtl())
selectionRect.move(controller.totalAdvance().width() - afterWidth, 0);
else
selectionRect.move(beforeWidth, 0);
selectionRect.setWidth(LayoutUnit::fromFloatCeil(afterWidth - beforeWidth));
}
int FontCascade::offsetForPositionForSimpleText(const TextRun& run, float x, bool includePartialGlyphs) const
{
float delta = x;
WidthIterator it(*this, run);
GlyphBuffer localGlyphBuffer;
unsigned offset;
if (run.rtl()) {
delta -= floatWidthForSimpleText(run);
while (1) {
offset = it.currentCharacterIndex();
float w;
if (!it.advanceOneCharacter(w, localGlyphBuffer))
break;
delta += w;
if (includePartialGlyphs) {
if (delta - w / 2 >= 0)
break;
} else {
if (delta >= 0)
break;
}
}
} else {
while (1) {
offset = it.currentCharacterIndex();
float w;
if (!it.advanceOneCharacter(w, localGlyphBuffer))
break;
delta -= w;
if (includePartialGlyphs) {
if (delta + w / 2 <= 0)
break;
} else {
if (delta <= 0)
break;
}
}
}
it.finalize(localGlyphBuffer);
return offset;
}
int FontCascade::offsetForPositionForComplexText(const TextRun& run, float x, bool includePartialGlyphs) const
{
ComplexTextController controller(*this, run);
return controller.offsetForPosition(x, includePartialGlyphs);
}
#if !PLATFORM(COCOA) && !USE(HARFBUZZ)
const Font* FontCascade::fontForCombiningCharacterSequence(const UChar* characters, size_t length) const
{
UChar32 baseCharacter;
size_t baseCharacterLength = 0;
U16_NEXT(characters, baseCharacterLength, length, baseCharacter);
GlyphData baseCharacterGlyphData = glyphDataForCharacter(baseCharacter, false, NormalVariant);
if (!baseCharacterGlyphData.glyph)
return nullptr;
return baseCharacterGlyphData.font;
}
#endif
struct GlyphIterationState {
FloatPoint startingPoint;
FloatPoint currentPoint;
float y1;
float y2;
float minX;
float maxX;
};
static Optional<float> findIntersectionPoint(float y, FloatPoint p1, FloatPoint p2)
{
if ((p1.y() < y && p2.y() > y) || (p1.y() > y && p2.y() < y))
return p1.x() + (y - p1.y()) * (p2.x() - p1.x()) / (p2.y() - p1.y());
return WTF::nullopt;
}
static void updateX(GlyphIterationState& state, float x)
{
state.minX = std::min(state.minX, x);
state.maxX = std::max(state.maxX, x);
}
static void findPathIntersections(GlyphIterationState& state, const PathElement& element)
{
bool doIntersection = false;
FloatPoint point = FloatPoint();
switch (element.type) {
case PathElement::Type::MoveToPoint:
state.startingPoint = element.points[0];
state.currentPoint = element.points[0];
break;
case PathElement::Type::AddLineToPoint:
doIntersection = true;
point = element.points[0];
break;
case PathElement::Type::AddQuadCurveToPoint:
doIntersection = true;
point = element.points[1];
break;
case PathElement::Type::AddCurveToPoint:
doIntersection = true;
point = element.points[2];
break;
case PathElement::Type::CloseSubpath:
doIntersection = true;
point = state.startingPoint;
break;
}
if (!doIntersection)
return;
if (auto intersectionPoint = findIntersectionPoint(state.y1, state.currentPoint, point))
updateX(state, *intersectionPoint);
if (auto intersectionPoint = findIntersectionPoint(state.y2, state.currentPoint, point))
updateX(state, *intersectionPoint);
if ((state.currentPoint.y() >= state.y1 && state.currentPoint.y() <= state.y2)
|| (state.currentPoint.y() <= state.y1 && state.currentPoint.y() >= state.y2))
updateX(state, state.currentPoint.x());
state.currentPoint = point;
}
class GlyphToPathTranslator {
public:
GlyphToPathTranslator(const TextRun& textRun, const GlyphBuffer& glyphBuffer, const FloatPoint& textOrigin)
: m_index(0)
, m_textRun(textRun)
, m_glyphBuffer(glyphBuffer)
, m_fontData(&glyphBuffer.fontAt(m_index))
, m_translation(AffineTransform::translation(textOrigin.x(), textOrigin.y()))
{
#if USE(CG)
m_translation.flipY();
#endif
}
bool containsMorePaths() { return m_index != m_glyphBuffer.size(); }
Path path();
std::pair<float, float> extents();
GlyphUnderlineType underlineType();
void advance();
private:
unsigned m_index;
const TextRun& m_textRun;
const GlyphBuffer& m_glyphBuffer;
const Font* m_fontData;
AffineTransform m_translation;
};
Path GlyphToPathTranslator::path()
{
Path path = m_fontData->pathForGlyph(m_glyphBuffer.glyphAt(m_index));
path.transform(m_translation);
return path;
}
std::pair<float, float> GlyphToPathTranslator::extents()
{
auto beginning = m_translation.mapPoint(FloatPoint(0, 0));
auto advance = m_glyphBuffer.advanceAt(m_index);
auto end = m_translation.mapSize(FloatSize(advance.width(), advance.height()));
return std::make_pair(beginning.x(), beginning.x() + end.width());
}
auto GlyphToPathTranslator::underlineType() -> GlyphUnderlineType
{
return computeUnderlineType(m_textRun, m_glyphBuffer, m_index);
}
void GlyphToPathTranslator::advance()
{
GlyphBufferAdvance advance = m_glyphBuffer.advanceAt(m_index);
m_translation.translate(FloatSize(advance.width(), advance.height()));
++m_index;
if (m_index < m_glyphBuffer.size())
m_fontData = &m_glyphBuffer.fontAt(m_index);
}
DashArray FontCascade::dashesForIntersectionsWithRect(const TextRun& run, const FloatPoint& textOrigin, const FloatRect& lineExtents) const
{
if (isLoadingCustomFonts())
return DashArray();
auto glyphBuffer = layoutText(codePath(run), run, 0, run.length());
if (!glyphBuffer.size())
return DashArray();
FloatPoint origin = textOrigin + FloatSize(glyphBuffer.initialAdvance());
GlyphToPathTranslator translator(run, glyphBuffer, origin);
DashArray result;
for (unsigned index = 0; translator.containsMorePaths(); ++index, translator.advance()) {
GlyphIterationState info = { FloatPoint(0, 0), FloatPoint(0, 0), lineExtents.y(), lineExtents.y() + lineExtents.height(), lineExtents.x() + lineExtents.width(), lineExtents.x() };
switch (translator.underlineType()) {
case GlyphUnderlineType::SkipDescenders: {
Path path = translator.path();
path.apply([&](const PathElement& element) {
findPathIntersections(info, element);
});
if (info.minX < info.maxX) {
result.append(info.minX - lineExtents.x());
result.append(info.maxX - lineExtents.x());
}
break;
}
case GlyphUnderlineType::SkipGlyph: {
std::pair<float, float> extents = translator.extents();
result.append(extents.first - lineExtents.x());
result.append(extents.second - lineExtents.x());
break;
}
case GlyphUnderlineType::DrawOverGlyph:
break;
}
}
return result;
}
}