#include "config.h"
#include "Font.h"
#include "CharacterProperties.h"
#include "FloatRect.h"
#include "FontCache.h"
#include "GlyphBuffer.h"
#include "LayoutRect.h"
#include "TextRun.h"
#include "WidthIterator.h"
#include <wtf/MainThread.h>
#include <wtf/MathExtras.h>
#include <wtf/text/AtomicStringHash.h>
#include <wtf/text/StringBuilder.h>
using namespace WTF;
using namespace Unicode;
namespace WTF {
template <> void deleteOwnedPtr<WebCore::TextLayout>(WebCore::TextLayout* ptr)
{
WebCore::Font::deleteLayout(ptr);
}
}
namespace WebCore {
static PassRef<FontGlyphs> retrieveOrAddCachedFontGlyphs(const FontDescription&, PassRefPtr<FontSelector>);
const uint8_t Font::s_roundingHackCharacterTable[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 1 , 1 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static bool isDrawnWithSVGFont(const TextRun& run)
{
return run.renderingContext();
}
static bool useBackslashAsYenSignForFamily(const AtomicString& family)
{
if (family.isEmpty())
return false;
static HashSet<AtomicString>* set;
if (!set) {
set = new HashSet<AtomicString>;
set->add("MS PGothic");
UChar unicodeNameMSPGothic[] = {0xFF2D, 0xFF33, 0x0020, 0xFF30, 0x30B4, 0x30B7, 0x30C3, 0x30AF};
set->add(AtomicString(unicodeNameMSPGothic, WTF_ARRAY_LENGTH(unicodeNameMSPGothic)));
set->add("MS PMincho");
UChar unicodeNameMSPMincho[] = {0xFF2D, 0xFF33, 0x0020, 0xFF30, 0x660E, 0x671D};
set->add(AtomicString(unicodeNameMSPMincho, WTF_ARRAY_LENGTH(unicodeNameMSPMincho)));
set->add("MS Gothic");
UChar unicodeNameMSGothic[] = {0xFF2D, 0xFF33, 0x0020, 0x30B4, 0x30B7, 0x30C3, 0x30AF};
set->add(AtomicString(unicodeNameMSGothic, WTF_ARRAY_LENGTH(unicodeNameMSGothic)));
set->add("MS Mincho");
UChar unicodeNameMSMincho[] = {0xFF2D, 0xFF33, 0x0020, 0x660E, 0x671D};
set->add(AtomicString(unicodeNameMSMincho, WTF_ARRAY_LENGTH(unicodeNameMSMincho)));
set->add("Meiryo");
UChar unicodeNameMeiryo[] = {0x30E1, 0x30A4, 0x30EA, 0x30AA};
set->add(AtomicString(unicodeNameMeiryo, WTF_ARRAY_LENGTH(unicodeNameMeiryo)));
}
return set->contains(family);
}
Font::CodePath Font::s_codePath = Auto;
TypesettingFeatures Font::s_defaultTypesettingFeatures = 0;
Font::Font()
: m_letterSpacing(0)
, m_wordSpacing(0)
, m_useBackslashAsYenSymbol(false)
, m_typesettingFeatures(0)
{
}
Font::Font(const FontDescription& fd, float letterSpacing, float wordSpacing)
: m_fontDescription(fd)
, m_letterSpacing(letterSpacing)
, m_wordSpacing(wordSpacing)
, m_useBackslashAsYenSymbol(useBackslashAsYenSignForFamily(fd.firstFamily()))
, m_typesettingFeatures(computeTypesettingFeatures())
{
}
Font::Font(const FontPlatformData& fontData, bool isPrinterFont, FontSmoothingMode fontSmoothingMode)
: m_glyphs(FontGlyphs::createForPlatformFont(fontData))
, m_letterSpacing(0)
, m_wordSpacing(0)
, m_useBackslashAsYenSymbol(false)
, m_typesettingFeatures(computeTypesettingFeatures())
{
m_fontDescription.setUsePrinterFont(isPrinterFont);
m_fontDescription.setFontSmoothing(fontSmoothingMode);
#if PLATFORM(IOS)
m_fontDescription.setSpecifiedSize(CTFontGetSize(fontData.font()));
m_fontDescription.setComputedSize(CTFontGetSize(fontData.font()));
m_fontDescription.setItalic(CTFontGetSymbolicTraits(fontData.font()) & kCTFontTraitItalic);
m_fontDescription.setWeight((CTFontGetSymbolicTraits(fontData.font()) & kCTFontTraitBold) ? FontWeightBold : FontWeightNormal);
#endif
}
#if PLATFORM(IOS)
Font::Font(const FontPlatformData& fontData, PassRefPtr<FontSelector> fontSelector)
: m_glyphs(FontGlyphs::createForPlatformFont(fontData))
, m_letterSpacing(0)
, m_wordSpacing(0)
, m_typesettingFeatures(computeTypesettingFeatures())
{
CTFontRef primaryFont = fontData.font();
m_fontDescription.setSpecifiedSize(CTFontGetSize(primaryFont));
m_fontDescription.setComputedSize(CTFontGetSize(primaryFont));
m_fontDescription.setItalic(CTFontGetSymbolicTraits(primaryFont) & kCTFontTraitItalic);
m_fontDescription.setWeight((CTFontGetSymbolicTraits(primaryFont) & kCTFontTraitBold) ? FontWeightBold : FontWeightNormal);
m_fontDescription.setUsePrinterFont(fontData.isPrinterFont());
m_glyphs = retrieveOrAddCachedFontGlyphs(m_fontDescription, fontSelector.get());
}
#endif
Font::Font(const Font& other)
: m_fontDescription(other.m_fontDescription)
, m_glyphs(other.m_glyphs)
, m_letterSpacing(other.m_letterSpacing)
, m_wordSpacing(other.m_wordSpacing)
, m_useBackslashAsYenSymbol(other.m_useBackslashAsYenSymbol)
, m_typesettingFeatures(computeTypesettingFeatures())
{
}
Font& Font::operator=(const Font& other)
{
m_fontDescription = other.m_fontDescription;
m_glyphs = other.m_glyphs;
m_letterSpacing = other.m_letterSpacing;
m_wordSpacing = other.m_wordSpacing;
m_useBackslashAsYenSymbol = other.m_useBackslashAsYenSymbol;
m_typesettingFeatures = other.m_typesettingFeatures;
return *this;
}
bool Font::operator==(const Font& other) const
{
if (loadingCustomFonts() || other.loadingCustomFonts())
return false;
if (m_fontDescription != other.m_fontDescription || m_letterSpacing != other.m_letterSpacing || m_wordSpacing != other.m_wordSpacing)
return false;
if (m_glyphs == other.m_glyphs)
return true;
if (!m_glyphs || !other.m_glyphs)
return false;
if (m_glyphs->fontSelector() != other.m_glyphs->fontSelector())
return false;
if (m_glyphs->fontSelectorVersion() != other.m_glyphs->fontSelectorVersion())
return false;
if (m_glyphs->generation() != other.m_glyphs->generation())
return false;
return true;
}
struct FontGlyphsCacheKey {
FontDescriptionFontDataCacheKey fontDescriptionCacheKey;
Vector<AtomicString, 3> families;
unsigned fontSelectorId;
unsigned fontSelectorVersion;
unsigned fontSelectorFlags;
};
struct FontGlyphsCacheEntry {
WTF_MAKE_FAST_ALLOCATED;
public:
FontGlyphsCacheEntry(FontGlyphsCacheKey&& k, PassRef<FontGlyphs> g) : key(WTF::move(k)), glyphs(WTF::move(g)) { }
FontGlyphsCacheKey key;
Ref<FontGlyphs> glyphs;
};
typedef HashMap<unsigned, OwnPtr<FontGlyphsCacheEntry>, AlreadyHashed> FontGlyphsCache;
static bool operator==(const FontGlyphsCacheKey& a, const FontGlyphsCacheKey& b)
{
if (a.fontDescriptionCacheKey != b.fontDescriptionCacheKey)
return false;
if (a.fontSelectorId != b.fontSelectorId || a.fontSelectorVersion != b.fontSelectorVersion || a.fontSelectorFlags != b.fontSelectorFlags)
return false;
if (a.families.size() != b.families.size())
return false;
for (unsigned i = 0; i < a.families.size(); ++i) {
if (!equalIgnoringCase(a.families[i].impl(), b.families[i].impl()))
return false;
}
return true;
}
static FontGlyphsCache& fontGlyphsCache()
{
DEPRECATED_DEFINE_STATIC_LOCAL(FontGlyphsCache, cache, ());
return cache;
}
void invalidateFontGlyphsCache()
{
fontGlyphsCache().clear();
}
void clearWidthCaches()
{
for (auto it = fontGlyphsCache().begin(), end = fontGlyphsCache().end(); it != end; ++it)
it->value->glyphs.get().widthCache().clear();
}
static unsigned makeFontSelectorFlags(const FontDescription& description)
{
return static_cast<unsigned>(description.script()) << 1 | static_cast<unsigned>(description.smallCaps());
}
static void makeFontGlyphsCacheKey(FontGlyphsCacheKey& key, const FontDescription& description, FontSelector* fontSelector)
{
key.fontDescriptionCacheKey = FontDescriptionFontDataCacheKey(description);
for (unsigned i = 0; i < description.familyCount(); ++i)
key.families.append(description.familyAt(i));
key.fontSelectorId = fontSelector ? fontSelector->uniqueId() : 0;
key.fontSelectorVersion = fontSelector ? fontSelector->version() : 0;
key.fontSelectorFlags = fontSelector && fontSelector->resolvesFamilyFor(description) ? makeFontSelectorFlags(description) : 0;
}
static unsigned computeFontGlyphsCacheHash(const FontGlyphsCacheKey& key)
{
Vector<unsigned, 7> hashCodes;
hashCodes.reserveInitialCapacity(4 + key.families.size());
hashCodes.uncheckedAppend(key.fontDescriptionCacheKey.computeHash());
hashCodes.uncheckedAppend(key.fontSelectorId);
hashCodes.uncheckedAppend(key.fontSelectorVersion);
hashCodes.uncheckedAppend(key.fontSelectorFlags);
for (unsigned i = 0; i < key.families.size(); ++i)
hashCodes.uncheckedAppend(key.families[i].impl() ? CaseFoldingHash::hash(key.families[i]) : 0);
return StringHasher::hashMemory(hashCodes.data(), hashCodes.size() * sizeof(unsigned));
}
void pruneUnreferencedEntriesFromFontGlyphsCache()
{
Vector<unsigned, 50> toRemove;
FontGlyphsCache::iterator end = fontGlyphsCache().end();
for (FontGlyphsCache::iterator it = fontGlyphsCache().begin(); it != end; ++it) {
if (it->value->glyphs.get().hasOneRef())
toRemove.append(it->key);
}
for (unsigned i = 0; i < toRemove.size(); ++i)
fontGlyphsCache().remove(toRemove[i]);
}
static PassRef<FontGlyphs> retrieveOrAddCachedFontGlyphs(const FontDescription& fontDescription, PassRefPtr<FontSelector> fontSelector)
{
FontGlyphsCacheKey key;
makeFontGlyphsCacheKey(key, fontDescription, fontSelector.get());
unsigned hash = computeFontGlyphsCacheHash(key);
FontGlyphsCache::AddResult addResult = fontGlyphsCache().add(hash, PassOwnPtr<FontGlyphsCacheEntry>());
if (!addResult.isNewEntry && addResult.iterator->value->key == key)
return addResult.iterator->value->glyphs.get();
OwnPtr<FontGlyphsCacheEntry>& newEntry = addResult.iterator->value;
newEntry = adoptPtr(new FontGlyphsCacheEntry(WTF::move(key), FontGlyphs::create(fontSelector)));
PassRef<FontGlyphs> glyphs = newEntry->glyphs.get();
static const unsigned unreferencedPruneInterval = 50;
static const int maximumEntries = 400;
static unsigned pruneCounter;
if (!(++pruneCounter % unreferencedPruneInterval))
pruneUnreferencedEntriesFromFontGlyphsCache();
if (fontGlyphsCache().size() > maximumEntries)
fontGlyphsCache().remove(fontGlyphsCache().begin());
return glyphs;
}
void Font::update(PassRefPtr<FontSelector> fontSelector) const
{
m_glyphs = retrieveOrAddCachedFontGlyphs(m_fontDescription, fontSelector.get());
m_useBackslashAsYenSymbol = useBackslashAsYenSignForFamily(firstFamily());
m_typesettingFeatures = computeTypesettingFeatures();
}
float Font::drawText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to, CustomFontNotReadyAction customFontNotReadyAction) const
{
if (loadingCustomFonts() && customFontNotReadyAction == DoNotPaintIfFontNotReady)
return 0;
to = (to == -1 ? run.length() : to);
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex && typesettingFeatures() && (from || to != run.length()) && !isDrawnWithSVGFont(run))
codePathToUse = Complex;
if (codePathToUse != Complex)
return drawSimpleText(context, run, point, from, to);
return drawComplexText(context, run, point, from, to);
}
void Font::drawEmphasisMarks(GraphicsContext* context, const TextRun& run, const AtomicString& mark, const FloatPoint& point, int from, int to) const
{
if (loadingCustomFonts())
return;
if (to < 0)
to = run.length();
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex && typesettingFeatures() && (from || to != run.length()) && !isDrawnWithSVGFont(run))
codePathToUse = Complex;
if (codePathToUse != Complex)
drawEmphasisMarksForSimpleText(context, run, mark, point, from, to);
else
drawEmphasisMarksForComplexText(context, run, mark, point, from, to);
}
float Font::width(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
{
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex) {
if (!canReturnFallbackFontsForComplexText())
fallbackFonts = 0;
if (codePathToUse != SimpleWithGlyphOverflow && (glyphOverflow && !glyphOverflow->computeBounds))
glyphOverflow = 0;
}
bool hasKerningOrLigatures = typesettingFeatures() & (Kerning | Ligatures);
bool hasWordSpacingOrLetterSpacing = wordSpacing() || letterSpacing();
float* cacheEntry = m_glyphs->widthCache().add(run, std::numeric_limits<float>::quiet_NaN(), hasKerningOrLigatures, hasWordSpacingOrLetterSpacing, glyphOverflow);
if (cacheEntry && !std::isnan(*cacheEntry))
return *cacheEntry;
HashSet<const SimpleFontData*> 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 Font::width(const TextRun& run, int& charsConsumed, String& glyphName) const
{
#if ENABLE(SVG_FONTS)
if (isDrawnWithSVGFont(run))
return run.renderingContext()->floatWidthUsingSVGFont(*this, run, charsConsumed, glyphName);
#endif
charsConsumed = run.length();
glyphName = "";
return width(run);
}
#if !PLATFORM(COCOA)
PassOwnPtr<TextLayout> Font::createLayout(RenderText*, float, bool) const
{
return nullptr;
}
void Font::deleteLayout(TextLayout*)
{
}
float Font::width(TextLayout&, unsigned, unsigned, HashSet<const SimpleFontData*>*)
{
ASSERT_NOT_REACHED();
return 0;
}
#endif
static const char* fontFamiliesWithInvalidCharWidth[] = {
"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",
};
bool Font::hasValidAverageCharWidth() const
{
AtomicString family = firstFamily();
if (family.isEmpty())
return false;
#if PLATFORM(MAC) || PLATFORM(IOS)
if (primaryFontDataIsSystemFont())
return false;
#endif
static HashSet<AtomicString>* fontFamiliesWithInvalidCharWidthMap = 0;
if (!fontFamiliesWithInvalidCharWidthMap) {
fontFamiliesWithInvalidCharWidthMap = new HashSet<AtomicString>;
for (size_t i = 0; i < WTF_ARRAY_LENGTH(fontFamiliesWithInvalidCharWidth); ++i)
fontFamiliesWithInvalidCharWidthMap->add(AtomicString(fontFamiliesWithInvalidCharWidth[i]));
}
return !fontFamiliesWithInvalidCharWidthMap->contains(family);
}
bool Font::fastAverageCharWidthIfAvailable(float& width) const
{
bool success = hasValidAverageCharWidth();
if (success)
width = roundf(primaryFont()->avgCharWidth()); return success;
}
void Font::adjustSelectionRectForText(const TextRun& run, LayoutRect& selectionRect, int from, int to) const
{
to = (to == -1 ? run.length() : to);
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex && typesettingFeatures() && (from || to != run.length()) && !isDrawnWithSVGFont(run))
codePathToUse = Complex;
if (codePathToUse != Complex)
return adjustSelectionRectForSimpleText(run, selectionRect, from, to);
return adjustSelectionRectForComplexText(run, selectionRect, from, to);
}
int Font::offsetForPosition(const TextRun& run, float x, bool includePartialGlyphs) const
{
if (codePath(run) != Complex && (!typesettingFeatures() || isDrawnWithSVGFont(run)))
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(Font::normalizeSpaces(characters[i]));
return normalized.toString();
}
String Font::normalizeSpaces(const LChar* characters, unsigned length)
{
return normalizeSpacesInternal(characters, length);
}
String Font::normalizeSpaces(const UChar* characters, unsigned length)
{
return normalizeSpacesInternal(characters, length);
}
static bool shouldUseFontSmoothing = true;
void Font::setShouldUseSmoothing(bool shouldUseSmoothing)
{
ASSERT(isMainThread());
shouldUseFontSmoothing = shouldUseSmoothing;
}
bool Font::shouldUseSmoothing()
{
return shouldUseFontSmoothing;
}
void Font::setCodePath(CodePath p)
{
s_codePath = p;
}
Font::CodePath Font::codePath()
{
return s_codePath;
}
void Font::setDefaultTypesettingFeatures(TypesettingFeatures typesettingFeatures)
{
s_defaultTypesettingFeatures = typesettingFeatures;
}
TypesettingFeatures Font::defaultTypesettingFeatures()
{
return s_defaultTypesettingFeatures;
}
Font::CodePath Font::codePath(const TextRun& run) const
{
if (s_codePath != Auto)
return s_codePath;
#if ENABLE(SVG_FONTS)
if (isDrawnWithSVGFont(run))
return Simple;
#endif
if (m_fontDescription.featureSettings() && m_fontDescription.featureSettings()->size() > 0)
return Complex;
if (run.length() > 1 && !WidthIterator::supportsTypesettingFeatures(*this))
return Complex;
if (!run.characterScanForCodePath())
return Simple;
if (run.is8Bit())
return Simple;
return characterRangeCodePath(run.characters16(), run.length());
}
Font::CodePath Font::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 < 0x2CEF) continue;
if (c <= 0x2CF1)
return Complex;
if (c < 0x302A) continue;
if (c <= 0x302F)
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 < 0x1F1E6) continue;
if (supplementaryCharacter <= 0x1F1FF)
return Complex;
if (supplementaryCharacter >= 0x1F466 && supplementaryCharacter <= 0x1F469) {
previousCharacterIsEmojiGroupCandidate = true;
continue;
}
if (isEmojiModifier(supplementaryCharacter))
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 Font::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 Font::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);
}
unsigned Font::expansionOpportunityCountInternal(const LChar* characters, size_t length, TextDirection direction, bool& isAfterExpansion)
{
unsigned count = 0;
if (direction == LTR) {
for (size_t i = 0; i < length; ++i) {
if (treatAsSpace(characters[i])) {
count++;
isAfterExpansion = true;
} else
isAfterExpansion = false;
}
} else {
for (size_t i = length; i > 0; --i) {
if (treatAsSpace(characters[i - 1])) {
count++;
isAfterExpansion = true;
} else
isAfterExpansion = false;
}
}
return count;
}
unsigned Font::expansionOpportunityCountInternal(const UChar* characters, size_t length, TextDirection direction, bool& isAfterExpansion)
{
static bool expandAroundIdeographs = canExpandAroundIdeographsInComplexText();
unsigned count = 0;
if (direction == LTR) {
for (size_t 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 (size_t 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;
}
}
return count;
}
unsigned Font::expansionOpportunityCount(const StringView& stringView, TextDirection direction, bool& isAfterExpansion)
{
if (stringView.is8Bit())
return expansionOpportunityCountInternal(stringView.characters8(), stringView.length(), direction, isAfterExpansion);
return expansionOpportunityCountInternal(stringView.characters16(), stringView.length(), direction, isAfterExpansion);
}
bool Font::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;
}
GlyphToPathTranslator::GlyphUnderlineType computeUnderlineType(const TextRun& textRun, const GlyphBuffer& glyphBuffer, int index)
{
UChar32 baseCharacter;
int offsetInString = glyphBuffer.offsetInString(index);
if (offsetInString == GlyphBuffer::kNoOffset) {
return GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph;
}
if (textRun.is8Bit())
baseCharacter = textRun.characters8()[offsetInString];
else
U16_NEXT(textRun.characters16(), offsetInString, textRun.length(), 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 GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph;
default:
return GlyphToPathTranslator::GlyphUnderlineType::SkipDescenders;
}
}
}