Get a fallback font for each missing character

Make the glyph layout engine look for a suitable font fallback for each
missing character, instead of using the first one found for all of them.

Solves #16404, #16213, maybe other garbled text issues.

Does not solve similar issues of getting slightly different positions and
widths for a whole string than for the sum of its parts due to subpixel
rendering (cf #15007 comment 2).

Change-Id: I40664e32c8ecd5f09e10aa7a60fcdaec230471b6
Reviewed-on: https://review.haiku-os.org/c/haiku/+/3273
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Máximo Castañeda
2020-12-01 11:45:27 +00:00
committed by Adrien Destugues
parent bca2f73564
commit a1c9aa9d90
+134 -137
View File
@@ -18,6 +18,8 @@
#include <Autolock.h> #include <Autolock.h>
#include <Debug.h> #include <Debug.h>
#include <ObjectList.h>
#include <SupportDefs.h>
#include <ctype.h> #include <ctype.h>
@@ -35,16 +37,46 @@ public:
Unset(); Unset();
} }
void SetTo(FontCacheEntry* entry, bool writeLocked) bool SetTo(FontCacheEntry* entry, bool writeLock)
{ {
// NOTE: If the semantics are changed such ASSERT(entry != NULL);
// that the reference to a previous entry
// is properly released, then don't forget if (entry == fCacheEntry) {
// to adapt existing which transfers if (writeLock == fWriteLocked)
// responsibility of entries between return true;
// references! UnlockAndDisown();
} else if (fCacheEntry != NULL)
Unset();
if (writeLock) {
if (!entry->WriteLock()) {
FontCache::Default()->Recycle(entry);
return false;
}
} else if (!entry->ReadLock()) {
FontCache::Default()->Recycle(entry);
return false;
}
fCacheEntry = entry; fCacheEntry = entry;
fWriteLocked = writeLocked; fWriteLocked = writeLock;
return true;
}
FontCacheEntry* UnlockAndDisown()
{
if (fCacheEntry == NULL)
return NULL;
if (fWriteLocked)
fCacheEntry->WriteUnlock();
else
fCacheEntry->ReadUnlock();
FontCacheEntry* entry = fCacheEntry;
fCacheEntry = NULL;
fWriteLocked = false;
return entry;
} }
void Unset() void Unset()
@@ -52,12 +84,7 @@ public:
if (fCacheEntry == NULL) if (fCacheEntry == NULL)
return; return;
if (fWriteLocked) FontCache::Default()->Recycle(UnlockAndDisown());
fCacheEntry->WriteUnlock();
else
fCacheEntry->ReadUnlock();
FontCache::Default()->Recycle(fCacheEntry);
} }
inline FontCacheEntry* Entry() const inline FontCacheEntry* Entry() const
@@ -81,11 +108,7 @@ public:
static bool IsWhiteSpace(uint32 glyphCode); static bool IsWhiteSpace(uint32 glyphCode);
static FontCacheEntry* FontCacheEntryFor(const ServerFont& font, static FontCacheEntry* FontCacheEntryFor(const ServerFont& font,
bool forceVector, bool forceVector);
const FontCacheEntry* disallowedEntry,
uint32 glyphCode,
FontCacheReference& cacheReference,
bool needsWriteLock);
template<class GlyphConsumer> template<class GlyphConsumer>
static bool LayoutGlyphs(GlyphConsumer& consumer, static bool LayoutGlyphs(GlyphConsumer& consumer,
@@ -98,13 +121,15 @@ public:
FontCacheReference* cacheReference = NULL); FontCacheReference* cacheReference = NULL);
private: private:
static bool _WriteLockAndAcquireFallbackEntry( static const GlyphCache* _CreateGlyph(
FontCacheReference& cacheReference, FontCacheReference& cacheReference,
FontCacheEntry* entry, BObjectList<FontCacheReference>& fallbacks,
const ServerFont& font, bool needsVector, const ServerFont& font, bool needsVector,
uint32 glyphCode, uint32 glyphCode);
FontCacheReference& fallbackCacheReference,
FontCacheEntry*& fallbackEntry); static void _PopulateAndLockFallbacks(
BObjectList<FontCacheReference>& fallbacks,
const ServerFont& font, bool forceVector);
GlyphLayoutEngine(); GlyphLayoutEngine();
virtual ~GlyphLayoutEngine(); virtual ~GlyphLayoutEngine();
@@ -132,44 +157,10 @@ GlyphLayoutEngine::IsWhiteSpace(uint32 charCode)
inline FontCacheEntry* inline FontCacheEntry*
GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, bool forceVector, GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, bool forceVector)
const FontCacheEntry* disallowedEntry, uint32 glyphCode,
FontCacheReference& cacheReference, bool needsWriteLock)
{ {
ASSERT(cacheReference.Entry() == NULL);
FontCache* cache = FontCache::Default(); FontCache* cache = FontCache::Default();
FontCacheEntry* entry = cache->FontCacheEntryFor(font, forceVector); FontCacheEntry* entry = cache->FontCacheEntryFor(font, forceVector);
if (entry == NULL)
return NULL;
if (entry == disallowedEntry) {
cache->Recycle(entry);
return NULL;
}
if (glyphCode != 0 && !entry->CanCreateGlyph(glyphCode)) {
cache->Recycle(entry);
return NULL;
}
if (needsWriteLock) {
if (!entry->WriteLock()) {
cache->Recycle(entry);
return NULL;
}
} else {
if (!entry->ReadLock()) {
cache->Recycle(entry);
return NULL;
}
}
// At this point, we have a valid FontCacheEntry and it is locked in the
// proper mode. We can setup the FontCacheReference so it takes care of
// the locking and recycling from now and return the entry.
cacheReference.SetTo(entry, needsWriteLock);
return entry; return entry;
} }
@@ -184,10 +175,12 @@ GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
{ {
// TODO: implement spacing modes // TODO: implement spacing modes
FontCacheEntry* entry = NULL; FontCacheEntry* entry = NULL;
FontCacheReference* pCacheReference;
FontCacheReference cacheReference; FontCacheReference cacheReference;
FontCacheEntry* fallbackEntry = NULL; BObjectList<FontCacheReference> fallbacksList(21, true);
FontCacheReference fallbackCacheReference;
if (_cacheReference != NULL) { if (_cacheReference != NULL) {
pCacheReference = _cacheReference;
entry = _cacheReference->Entry(); entry = _cacheReference->Entry();
// When there is already a cacheReference, it means there was already // When there is already a cacheReference, it means there was already
// an iteration over the glyphs. The use-case is for example to do // an iteration over the glyphs. The use-case is for example to do
@@ -196,14 +189,16 @@ GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
// This means that the fallback entry mechanism will not do any good // This means that the fallback entry mechanism will not do any good
// for the second pass, since the fallback glyphs have been stored in // for the second pass, since the fallback glyphs have been stored in
// the original entry. // the original entry.
} } else
pCacheReference = &cacheReference;
if (entry == NULL) { if (entry == NULL) {
entry = FontCacheEntryFor(font, consumer.NeedsVector(), NULL, 0, entry = FontCacheEntryFor(font, consumer.NeedsVector());
cacheReference, false);
if (entry == NULL) if (entry == NULL)
return false; return false;
if (!pCacheReference->SetTo(entry, false))
return false;
} // else the entry was already used and is still locked } // else the entry was already used and is still locked
consumer.Start(); consumer.Start();
@@ -222,7 +217,6 @@ GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
uint32 lastCharCode = 0; // Needed for kerning in B_STRING_SPACING mode uint32 lastCharCode = 0; // Needed for kerning in B_STRING_SPACING mode
uint32 charCode; uint32 charCode;
int32 index = 0; int32 index = 0;
bool writeLocked = false;
const char* start = utf8String; const char* start = utf8String;
while (maxChars-- > 0 && (charCode = UTF8ToCharCode(&utf8String)) != 0) { while (maxChars-- > 0 && (charCode = UTF8ToCharCode(&utf8String)) != 0) {
@@ -241,18 +235,12 @@ GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
const GlyphCache* glyph = entry->CachedGlyph(charCode); const GlyphCache* glyph = entry->CachedGlyph(charCode);
if (glyph == NULL) { if (glyph == NULL) {
// The glyph has not been cached yet, switch to a write lock, glyph = _CreateGlyph(*pCacheReference, fallbacksList, font,
// acquire the fallback entry and create the glyph. Note that consumer.NeedsVector(), charCode);
// the write lock will persist (in the cacheReference) so that
// we only have to do this switch once for the whole string.
if (!writeLocked) {
writeLocked = _WriteLockAndAcquireFallbackEntry(cacheReference,
entry, font, consumer.NeedsVector(), charCode,
fallbackCacheReference, fallbackEntry);
}
if (writeLocked) // Something may have gone wrong while reacquiring the entry lock
glyph = entry->CreateGlyph(charCode, fallbackEntry); if (pCacheReference->Entry() == NULL)
return false;
} }
if (glyph == NULL) { if (glyph == NULL) {
@@ -292,40 +280,55 @@ GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
y += advanceY; y += advanceY;
consumer.Finish(x, y); consumer.Finish(x, y);
if (_cacheReference != NULL && _cacheReference->Entry() == NULL) {
// The caller passed a FontCacheReference, but this is the first
// iteration -> switch the ownership from the stack allocated
// FontCacheReference to the one passed by the caller. The fallback
// FontCacheReference is not affected by this, since it is never used
// during a second iteration.
_cacheReference->SetTo(entry, cacheReference.WriteLocked());
cacheReference.SetTo(NULL, false);
}
return true; return true;
} }
inline bool inline const GlyphCache*
GlyphLayoutEngine::_WriteLockAndAcquireFallbackEntry( GlyphLayoutEngine::_CreateGlyph(FontCacheReference& cacheReference,
FontCacheReference& cacheReference, FontCacheEntry* entry, BObjectList<FontCacheReference>& fallbacks,
const ServerFont& font, bool forceVector, uint32 charCode, const ServerFont& font, bool forceVector, uint32 charCode)
FontCacheReference& fallbackCacheReference,
FontCacheEntry*& fallbackEntry)
{ {
// We need a fallback font, since potentially, we have to obtain missing FontCacheEntry* entry = cacheReference.Entry();
// glyphs from it. We need to obtain the fallback font while we have not
// locked anything, since locking the FontManager with the write-lock held
// can obvisouly lead to a deadlock.
bool writeLocked = entry->IsWriteLocked(); // Avoid loading and locking the fallbacks if our font can create the glyph.
if (entry->CanCreateGlyph(charCode)) {
if (writeLocked) { if (cacheReference.SetTo(entry, true))
entry->WriteUnlock(); return entry->CreateGlyph(charCode);
} else { return NULL;
cacheReference.SetTo(NULL, false);
entry->ReadUnlock();
} }
if (fallbacks.IsEmpty()) {
// We need to create new glyphs with the engine of the fallback font
// and store them in the main font cache (not just transfer them from
// one cache to the other). So we need both to be write-locked.
// The main font is unlocked first, in case it is also in the fallback
// list, so that we always keep the same order to avoid deadlocks.
cacheReference.UnlockAndDisown();
_PopulateAndLockFallbacks(fallbacks, font, forceVector);
if (!cacheReference.SetTo(entry, true)) {
return NULL;
}
}
int32 count = fallbacks.CountItems();
for (int32 index = 0; index < count; index++) {
FontCacheEntry* fallbackEntry = fallbacks.ItemAt(index)->Entry();
if (fallbackEntry->CanCreateGlyph(charCode))
return entry->CreateGlyph(charCode, fallbackEntry);
}
return NULL;
}
inline void
GlyphLayoutEngine::_PopulateAndLockFallbacks(
BObjectList<FontCacheReference>& fallbacksList,
const ServerFont& font, bool forceVector)
{
ASSERT(fallbacksList.IsEmpty());
// TODO: We always get the fallback glyphs from the Noto family, but of // TODO: We always get the fallback glyphs from the Noto family, but of
// course the fallback font should a) contain the missing glyphs at all // course the fallback font should a) contain the missing glyphs at all
// and b) be similar to the original font. So there should be a mapping // and b) be similar to the original font. So there should be a mapping
@@ -334,14 +337,18 @@ GlyphLayoutEngine::_WriteLockAndAcquireFallbackEntry(
"Noto Sans Display", "Noto Sans Display",
"Noto Sans Thai", "Noto Sans Thai",
"Noto Sans CJK JP", "Noto Sans CJK JP",
"Noto Sans Symbols", "Noto Sans Symbols"
NULL
}; };
fallbackEntry = NULL; if (!gFontManager->Lock())
return;
// Try to get the glyph from the fallback fonts. static const int nStyles = 3;
for (int c = 0; c < 3; c++) { static const int nFallbacks = B_COUNT_OF(fallbacks);
FontCacheEntry* fallbackCacheEntries[nStyles * nFallbacks];
int entries = 0;
for (int c = 0; c < nStyles; c++) {
const char* fontStyle; const char* fontStyle;
if (c == 0) if (c == 0)
fontStyle = font.Style(); fontStyle = font.Style();
@@ -350,10 +357,7 @@ GlyphLayoutEngine::_WriteLockAndAcquireFallbackEntry(
else else
fontStyle = NULL; fontStyle = NULL;
for (int i = 0; fallbacks[i] != NULL; i++) { for (int i = 0; i < nFallbacks; i++) {
BAutolock locker(gFontManager);
if (!locker.IsLocked())
continue;
FontStyle* fallbackStyle = gFontManager->GetStyle(fallbacks[i], FontStyle* fallbackStyle = gFontManager->GetStyle(fallbacks[i],
fontStyle, 0xffff, 0); fontStyle, 0xffff, 0);
@@ -362,41 +366,34 @@ GlyphLayoutEngine::_WriteLockAndAcquireFallbackEntry(
continue; continue;
ServerFont fallbackFont(*fallbackStyle, font.Size()); ServerFont fallbackFont(*fallbackStyle, font.Size());
locker.Unlock();
// Force the write-lock on the fallback entry, since we FontCacheEntry* entry = FontCacheEntryFor(
// don't transfer or copy GlyphCache objects from one cache fallbackFont, forceVector);
// to the other, but create new glyphs which are stored in
// "entry" in any case, which requires the write cache for if (entry == NULL)
// sure (used FontEngine of fallbackEntry). continue;
FontCacheEntry* candidateFallbackEntry = FontCacheEntryFor(
fallbackFont, forceVector, entry, charCode, fallbackCacheEntries[entries++] = entry;
fallbackCacheReference, true);
// Stop when we find a font that indeed has the glyph we need.
if (candidateFallbackEntry != NULL) {
fallbackEntry = candidateFallbackEntry;
break;
}
} }
if (fallbackEntry != NULL)
break;
} }
// NOTE: We don't care if fallbackEntry is still NULL, fetching gFontManager->Unlock();
// alternate glyphs will simply not work.
if (!entry->WriteLock()) { // Finally lock the entries and save their references
FontCache::Default()->Recycle(entry); for (int i = 0; i < entries; i++) {
return false; FontCacheReference* cacheReference =
new(std::nothrow) FontCacheReference();
if (cacheReference != NULL) {
if (cacheReference->SetTo(fallbackCacheEntries[i], true))
fallbacksList.AddItem(cacheReference);
else
delete cacheReference;
}
} }
if (!writeLocked) { return;
// Update the FontCacheReference, since the locking kind changed.
cacheReference.SetTo(entry, true);
}
return true;
} }