#include "config.h"
#include "Font.h"
#include "FloatRect.h"
#include "FontCache.h"
#include "FontTranscoder.h"
#include "IntPoint.h"
#include "GlyphBuffer.h"
#include "TextRun.h"
#include "WidthIterator.h"
#include <wtf/MainThread.h>
#include <wtf/MathExtras.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 {
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
};
Font::CodePath Font::s_codePath = Auto;
TypesettingFeatures Font::s_defaultTypesettingFeatures = 0;
Font::Font()
: m_letterSpacing(0)
, m_wordSpacing(0)
, m_needsTranscoding(false)
, m_typesettingFeatures(0)
{
}
Font::Font(const FontDescription& fd, short letterSpacing, short wordSpacing)
: m_fontDescription(fd)
, m_letterSpacing(letterSpacing)
, m_wordSpacing(wordSpacing)
, m_needsTranscoding(fontTranscoder().needsTranscoding(fd))
, m_typesettingFeatures(computeTypesettingFeatures())
{
}
Font::Font(const FontPlatformData& fontData, bool isPrinterFont, FontSmoothingMode fontSmoothingMode)
: m_glyphs(FontGlyphs::createForPlatformFont(fontData))
, m_letterSpacing(0)
, m_wordSpacing(0)
, m_typesettingFeatures(computeTypesettingFeatures())
{
m_fontDescription.setUsePrinterFont(isPrinterFont);
m_fontDescription.setFontSmoothing(fontSmoothingMode);
m_needsTranscoding = fontTranscoder().needsTranscoding(fontDescription());
}
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_needsTranscoding(other.m_needsTranscoding)
, 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_needsTranscoding = other.m_needsTranscoding;
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:
FontGlyphsCacheKey key;
RefPtr<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.families != b.families)
return false;
if (a.fontSelectorId != b.fontSelectorId || a.fontSelectorVersion != b.fontSelectorVersion || a.fontSelectorFlags != b.fontSelectorFlags)
return false;
return true;
}
static FontGlyphsCache& fontGlyphsCache()
{
DEFINE_STATIC_LOCAL(FontGlyphsCache, cache, ());
return cache;
}
void invalidateFontGlyphsCache()
{
fontGlyphsCache().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).lower());
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)
{
unsigned hashCodes[5] = {
StringHasher::hashMemory(key.families.data(), key.families.size() * sizeof(key.families[0])),
key.fontDescriptionCacheKey.computeHash(),
key.fontSelectorId,
key.fontSelectorVersion,
key.fontSelectorFlags
};
return StringHasher::hashMemory<sizeof(hashCodes)>(hashCodes);
}
void pruneUnreferencedEntriesFromFontGlyphsCache()
{
Vector<unsigned, 50> toRemove;
FontGlyphsCache::iterator end = fontGlyphsCache().end();
for (FontGlyphsCache::iterator it = fontGlyphsCache().begin(); it != end; ++it) {
if (it->value->glyphs->hasOneRef())
toRemove.append(it->key);
}
for (unsigned i = 0; i < toRemove.size(); ++i)
fontGlyphsCache().remove(toRemove[i]);
}
static PassRefPtr<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;
OwnPtr<FontGlyphsCacheEntry>& newEntry = addResult.iterator->value;
newEntry = adoptPtr(new FontGlyphsCacheEntry);
newEntry->glyphs = FontGlyphs::create(fontSelector);
newEntry->key = key;
RefPtr<FontGlyphs> glyphs = newEntry->glyphs;
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_typesettingFeatures = computeTypesettingFeatures();
}
void Font::drawText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to, CustomFontNotReadyAction customFontNotReadyAction) const
{
if (loadingCustomFonts() && customFontNotReadyAction == DoNotPaintIfFontNotReady)
return;
to = (to == -1 ? run.length() : to);
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex && typesettingFeatures() && (from || to != run.length()) && !run.renderingContext())
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()) && !run.renderingContext())
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 (TextRun::RenderingContext* renderingContext = run.renderingContext())
return renderingContext->floatWidthUsingSVGFont(*this, run, charsConsumed, glyphName);
#endif
charsConsumed = run.length();
glyphName = "";
return width(run);
}
#if !PLATFORM(MAC)
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
FloatRect Font::selectionRectForText(const TextRun& run, const FloatPoint& point, int h, int from, int to) const
{
to = (to == -1 ? run.length() : to);
CodePath codePathToUse = codePath(run);
if (codePathToUse != Complex && typesettingFeatures() && (from || to != run.length()) && !run.renderingContext())
codePathToUse = Complex;
if (codePathToUse != Complex)
return selectionRectForSimpleText(run, point, h, from, to);
return selectionRectForComplexText(run, point, h, from, to);
}
int Font::offsetForPosition(const TextRun& run, float x, bool includePartialGlyphs) const
{
if (codePath(run) != Complex && !typesettingFeatures())
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 (run.renderingContext())
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;
for (unsigned i = 0; i < len; i++) {
const UChar c = characters[i];
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 < 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 <= 0x1F6F)
return true;
return isCJKIdeograph(c);
}
unsigned Font::expansionOpportunityCount(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::expansionOpportunityCount(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;
}
bool Font::canReceiveTextEmphasis(UChar32 c)
{
CharCategory category = Unicode::category(c);
if (category & (Separator_Space | Separator_Line | Separator_Paragraph | Other_NotAssigned | Other_Control | Other_Format))
return false;
if (c == ethiopicWordspace || c == aegeanWordSeparatorLine || c == aegeanWordSeparatorDot
|| c == ugariticWordDivider || c == tibetanMarkIntersyllabicTsheg || c == tibetanMarkDelimiterTshegBstar)
return false;
return true;
}
}