#include "config.h"
#include "Font.h"
#if PLATFORM(COCOA)
#include "CoreTextSPI.h"
#endif
#include "FontCache.h"
#include "FontCascade.h"
#include "OpenTypeMathData.h"
#include <wtf/MathExtras.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/AtomicStringHash.h>
#if ENABLE(OPENTYPE_VERTICAL)
#include "OpenTypeVerticalData.h"
#endif
#if USE(DIRECT2D)
#include <dwrite.h>
#endif
namespace WebCore {
unsigned GlyphPage::s_count = 0;
const float smallCapsFontSizeMultiplier = 0.7f;
const float emphasisMarkFontSizeMultiplier = 0.5f;
Font::Font(const FontPlatformData& platformData, bool isCustomFont, bool isLoading, bool isTextOrientationFallback)
: m_maxCharWidth(-1)
, m_avgCharWidth(-1)
, m_platformData(platformData)
, m_mathData(nullptr)
, m_treatAsFixedPitch(false)
, m_isCustomFont(isCustomFont)
, m_isLoading(isLoading)
, m_isTextOrientationFallback(isTextOrientationFallback)
, m_isBrokenIdeographFallback(false)
, m_hasVerticalGlyphs(false)
, m_isUsedInSystemFallbackCache(false)
#if PLATFORM(IOS)
, m_shouldNotBeUsedForArabic(false)
#endif
{
platformInit();
platformGlyphInit();
platformCharWidthInit();
#if ENABLE(OPENTYPE_VERTICAL)
if (platformData.orientation() == Vertical && !isTextOrientationFallback) {
m_verticalData = FontCache::singleton().verticalData(platformData);
m_hasVerticalGlyphs = m_verticalData.get() && m_verticalData->hasVerticalMetrics();
}
#endif
}
void Font::initCharWidths()
{
auto* glyphPageZero = glyphPage(GlyphPage::pageNumberForCodePoint('0'));
if (m_avgCharWidth <= 0.f && glyphPageZero) {
Glyph digitZeroGlyph = glyphPageZero->glyphDataForCharacter('0').glyph;
if (digitZeroGlyph)
m_avgCharWidth = widthForGlyph(digitZeroGlyph);
}
if (m_avgCharWidth <= 0.f)
m_avgCharWidth = m_fontMetrics.xHeight();
if (m_maxCharWidth <= 0.f)
m_maxCharWidth = std::max(m_avgCharWidth, m_fontMetrics.floatAscent());
}
void Font::platformGlyphInit()
{
auto* glyphPageZero = glyphPage(0);
auto* glyphPageCharacterZero = glyphPage(GlyphPage::pageNumberForCodePoint('0'));
auto* glyphPageSpace = glyphPage(GlyphPage::pageNumberForCodePoint(space));
if (glyphPageZero)
m_zeroWidthSpaceGlyph = glyphPageZero->glyphDataForCharacter(0).glyph;
if (glyphPageSpace)
m_spaceGlyph = glyphPageSpace->glyphDataForCharacter(space).glyph;
float width = widthForGlyph(m_spaceGlyph);
m_spaceWidth = width;
if (glyphPageCharacterZero)
m_zeroGlyph = glyphPageCharacterZero->glyphDataForCharacter('0').glyph;
m_fontMetrics.setZeroWidth(widthForGlyph(m_zeroGlyph));
determinePitch();
m_adjustedSpaceWidth = m_treatAsFixedPitch ? ceilf(width) : roundf(width);
if (m_zeroWidthSpaceGlyph == m_spaceGlyph)
m_zeroWidthSpaceGlyph = 0;
}
Font::~Font()
{
removeFromSystemFallbackCache();
}
static bool fillGlyphPage(GlyphPage& pageToFill, UChar* buffer, unsigned bufferLength, const Font& font)
{
bool hasGlyphs = pageToFill.fill(buffer, bufferLength);
#if ENABLE(OPENTYPE_VERTICAL)
if (hasGlyphs && font.verticalData())
font.verticalData()->substituteWithVerticalGlyphs(&font, &pageToFill);
#else
UNUSED_PARAM(font);
#endif
return hasGlyphs;
}
static RefPtr<GlyphPage> createAndFillGlyphPage(unsigned pageNumber, const Font& font)
{
#if PLATFORM(IOS)
if (GlyphPage::pageNumberIsUsedForArabic(pageNumber) && font.shouldNotBeUsedForArabic())
return nullptr;
#endif
unsigned glyphPageSize = GlyphPage::sizeForPageNumber(pageNumber);
unsigned start = GlyphPage::startingCodePointInPageNumber(pageNumber);
unsigned end = start + glyphPageSize;
Vector<UChar> buffer(glyphPageSize * 2 + 2);
unsigned bufferLength;
if (U_IS_BMP(start)) {
bufferLength = glyphPageSize;
for (unsigned i = 0; i < bufferLength; i++)
buffer[i] = start + i;
auto overwriteCodePoints = [&](unsigned minimum, unsigned maximum, UChar newCodePoint) {
unsigned begin = std::max(start, minimum);
unsigned complete = std::min(end, maximum);
for (unsigned i = begin; i < complete; ++i)
buffer[i - start] = newCodePoint;
};
auto overwriteCodePoint = [&](UChar codePoint, UChar newCodePoint) {
if (codePoint >= start && codePoint < end)
buffer[codePoint - start] = newCodePoint;
};
overwriteCodePoints(0x0, 0x20, zeroWidthSpace);
overwriteCodePoints(0x7F, 0xA0, zeroWidthSpace);
overwriteCodePoint(softHyphen, zeroWidthSpace);
overwriteCodePoint('\n', space);
overwriteCodePoint('\t', space);
overwriteCodePoint(noBreakSpace, space);
overwriteCodePoint(narrowNoBreakSpace, zeroWidthSpace);
overwriteCodePoint(leftToRightMark, zeroWidthSpace);
overwriteCodePoint(rightToLeftMark, zeroWidthSpace);
overwriteCodePoint(leftToRightEmbed, zeroWidthSpace);
overwriteCodePoint(rightToLeftEmbed, zeroWidthSpace);
overwriteCodePoint(leftToRightOverride, zeroWidthSpace);
overwriteCodePoint(rightToLeftOverride, zeroWidthSpace);
overwriteCodePoint(leftToRightIsolate, zeroWidthSpace);
overwriteCodePoint(rightToLeftIsolate, zeroWidthSpace);
overwriteCodePoint(zeroWidthNonJoiner, zeroWidthSpace);
overwriteCodePoint(zeroWidthJoiner, zeroWidthSpace);
overwriteCodePoint(popDirectionalFormatting, zeroWidthSpace);
overwriteCodePoint(popDirectionalIsolate, zeroWidthSpace);
overwriteCodePoint(firstStrongIsolate, zeroWidthSpace);
overwriteCodePoint(objectReplacementCharacter, zeroWidthSpace);
overwriteCodePoint(zeroWidthNoBreakSpace, zeroWidthSpace);
} else {
bufferLength = glyphPageSize * 2;
for (unsigned i = 0; i < glyphPageSize; i++) {
int c = i + start;
buffer[i * 2] = U16_LEAD(c);
buffer[i * 2 + 1] = U16_TRAIL(c);
}
}
Ref<GlyphPage> glyphPage = GlyphPage::create(font);
bool haveGlyphs = fillGlyphPage(glyphPage, buffer.data(), bufferLength, font);
if (!haveGlyphs)
return nullptr;
return WTFMove(glyphPage);
}
const GlyphPage* Font::glyphPage(unsigned pageNumber) const
{
if (!pageNumber) {
if (!m_glyphPageZero)
m_glyphPageZero = createAndFillGlyphPage(0, *this);
return m_glyphPageZero.get();
}
auto addResult = m_glyphPages.add(pageNumber, nullptr);
if (addResult.isNewEntry)
addResult.iterator->value = createAndFillGlyphPage(pageNumber, *this);
return addResult.iterator->value.get();
}
Glyph Font::glyphForCharacter(UChar32 character) const
{
auto* page = glyphPage(GlyphPage::pageNumberForCodePoint(character));
if (!page)
return 0;
return page->glyphForCharacter(character);
}
GlyphData Font::glyphDataForCharacter(UChar32 character) const
{
auto* page = glyphPage(GlyphPage::pageNumberForCodePoint(character));
if (!page)
return GlyphData();
return page->glyphDataForCharacter(character);
}
const Font& Font::verticalRightOrientationFont() const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->verticalRightOrientation) {
auto verticalRightPlatformData = FontPlatformData::cloneWithOrientation(m_platformData, Horizontal);
m_derivedFontData->verticalRightOrientation = create(verticalRightPlatformData, isCustomFont(), false, true);
}
ASSERT(m_derivedFontData->verticalRightOrientation != this);
return *m_derivedFontData->verticalRightOrientation;
}
const Font& Font::uprightOrientationFont() const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->uprightOrientation)
m_derivedFontData->uprightOrientation = create(m_platformData, isCustomFont(), false, true);
ASSERT(m_derivedFontData->uprightOrientation != this);
return *m_derivedFontData->uprightOrientation;
}
const Font* Font::smallCapsFont(const FontDescription& fontDescription) const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->smallCaps)
m_derivedFontData->smallCaps = createScaledFont(fontDescription, smallCapsFontSizeMultiplier);
ASSERT(m_derivedFontData->smallCaps != this);
return m_derivedFontData->smallCaps.get();
}
#if PLATFORM(COCOA)
const Font& Font::noSynthesizableFeaturesFont() const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->noSynthesizableFeatures)
m_derivedFontData->noSynthesizableFeatures = createFontWithoutSynthesizableFeatures();
ASSERT(m_derivedFontData->noSynthesizableFeatures != this);
return *m_derivedFontData->noSynthesizableFeatures;
}
#endif
const Font* Font::emphasisMarkFont(const FontDescription& fontDescription) const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->emphasisMark)
m_derivedFontData->emphasisMark = createScaledFont(fontDescription, emphasisMarkFontSizeMultiplier);
ASSERT(m_derivedFontData->emphasisMark != this);
return m_derivedFontData->emphasisMark.get();
}
const Font& Font::brokenIdeographFont() const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->brokenIdeograph) {
m_derivedFontData->brokenIdeograph = create(m_platformData, isCustomFont(), false);
m_derivedFontData->brokenIdeograph->m_isBrokenIdeographFallback = true;
}
ASSERT(m_derivedFontData->brokenIdeograph != this);
return *m_derivedFontData->brokenIdeograph;
}
const Font& Font::nonSyntheticItalicFont() const
{
if (!m_derivedFontData)
m_derivedFontData = std::make_unique<DerivedFonts>(isCustomFont());
if (!m_derivedFontData->nonSyntheticItalic) {
#if PLATFORM(COCOA) || USE(CAIRO)
FontPlatformData nonSyntheticItalicFontPlatformData = FontPlatformData::cloneWithSyntheticOblique(m_platformData, false);
#else
FontPlatformData nonSyntheticItalicFontPlatformData(m_platformData);
#endif
m_derivedFontData->nonSyntheticItalic = create(nonSyntheticItalicFontPlatformData, isCustomFont());
}
ASSERT(m_derivedFontData->nonSyntheticItalic != this);
return *m_derivedFontData->nonSyntheticItalic;
}
#ifndef NDEBUG
String Font::description() const
{
if (isCustomFont())
return "[custom font]";
return platformData().description();
}
#endif
const OpenTypeMathData* Font::mathData() const
{
if (m_isLoading)
return nullptr;
if (!m_mathData) {
m_mathData = OpenTypeMathData::create(m_platformData);
if (!m_mathData->hasMathData())
m_mathData = nullptr;
}
return m_mathData.get();
}
Font::DerivedFonts::~DerivedFonts()
{
}
RefPtr<Font> Font::createScaledFont(const FontDescription& fontDescription, float scaleFactor) const
{
return platformCreateScaledFont(fontDescription, scaleFactor);
}
bool Font::applyTransforms(GlyphBufferGlyph* glyphs, GlyphBufferAdvance* advances, size_t glyphCount, bool enableKerning, bool requiresShaping) const
{
#if PLATFORM(COCOA)
CTFontTransformOptions options = (enableKerning ? kCTFontTransformApplyPositioning : 0) | (requiresShaping ? kCTFontTransformApplyShaping : 0);
return CTFontTransformGlyphs(m_platformData.ctFont(), glyphs, reinterpret_cast<CGSize*>(advances), glyphCount, options);
#else
UNUSED_PARAM(glyphs);
UNUSED_PARAM(advances);
UNUSED_PARAM(glyphCount);
UNUSED_PARAM(enableKerning);
UNUSED_PARAM(requiresShaping);
return false;
#endif
}
class CharacterFallbackMapKey {
public:
CharacterFallbackMapKey()
{
}
CharacterFallbackMapKey(const AtomicString& locale, UChar32 character, bool isForPlatformFont)
: locale(locale)
, character(character)
, isForPlatformFont(isForPlatformFont)
{
}
CharacterFallbackMapKey(WTF::HashTableDeletedValueType)
: character(-1)
{
}
bool isHashTableDeletedValue() const { return character == -1; }
bool operator==(const CharacterFallbackMapKey& other) const
{
return locale == other.locale && character == other.character && isForPlatformFont == other.isForPlatformFont;
}
static const bool emptyValueIsZero = true;
private:
friend struct CharacterFallbackMapKeyHash;
AtomicString locale;
UChar32 character { 0 };
bool isForPlatformFont { false };
};
struct CharacterFallbackMapKeyHash {
static unsigned hash(const CharacterFallbackMapKey& key)
{
IntegerHasher hasher;
hasher.add(key.character);
hasher.add(key.isForPlatformFont);
hasher.add(key.locale.existingHash());
return hasher.hash();
}
static bool equal(const CharacterFallbackMapKey& a, const CharacterFallbackMapKey& b)
{
return a == b;
}
static const bool safeToCompareToEmptyOrDeleted = true;
};
typedef HashMap<CharacterFallbackMapKey, Font*, CharacterFallbackMapKeyHash, WTF::SimpleClassHashTraits<CharacterFallbackMapKey>> CharacterFallbackMap;
typedef HashMap<const Font*, CharacterFallbackMap> SystemFallbackCache;
static SystemFallbackCache& systemFallbackCache()
{
static NeverDestroyed<SystemFallbackCache> map;
return map.get();
}
RefPtr<Font> Font::systemFallbackFontForCharacter(UChar32 character, const FontDescription& description, bool isForPlatformFont) const
{
auto fontAddResult = systemFallbackCache().add(this, CharacterFallbackMap());
if (!character) {
UChar codeUnit = 0;
return FontCache::singleton().systemFallbackForCharacters(description, this, isForPlatformFont, &codeUnit, 1);
}
auto key = CharacterFallbackMapKey(description.locale(), character, isForPlatformFont);
auto characterAddResult = fontAddResult.iterator->value.add(WTFMove(key), nullptr);
Font*& fallbackFont = characterAddResult.iterator->value;
if (!fallbackFont) {
UChar codeUnits[2];
unsigned codeUnitsLength;
if (U_IS_BMP(character)) {
codeUnits[0] = FontCascade::normalizeSpaces(character);
codeUnitsLength = 1;
} else {
codeUnits[0] = U16_LEAD(character);
codeUnits[1] = U16_TRAIL(character);
codeUnitsLength = 2;
}
fallbackFont = FontCache::singleton().systemFallbackForCharacters(description, this, isForPlatformFont, codeUnits, codeUnitsLength).get();
if (fallbackFont)
fallbackFont->m_isUsedInSystemFallbackCache = true;
}
return fallbackFont;
}
void Font::removeFromSystemFallbackCache()
{
systemFallbackCache().remove(this);
if (!m_isUsedInSystemFallbackCache)
return;
for (auto& characterMap : systemFallbackCache().values()) {
Vector<CharacterFallbackMapKey, 512> toRemove;
for (auto& entry : characterMap) {
if (entry.value == this)
toRemove.append(entry.key);
}
for (auto& key : toRemove)
characterMap.remove(key);
}
}
}