* Copyright 2007-2022, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef GLYPH_LAYOUT_ENGINE_H
#define GLYPH_LAYOUT_ENGINE_H
#include "utf8_functions.h"
#include "FontCache.h"
#include "FontCacheEntry.h"
#include "GlobalFontManager.h"
#include "ServerFont.h"
#include <Autolock.h>
#include <Debug.h>
#include <ObjectList.h>
#include <SupportDefs.h>
#include <ctype.h>
class FontCacheReference {
public:
FontCacheReference()
:
fCacheEntry(NULL),
fFallbackReference(NULL),
fLocked(false),
fWriteLocked(false)
{
}
~FontCacheReference()
{
if (fCacheEntry == NULL)
return;
fFallbackReference = NULL;
Unlock();
if (fCacheEntry != NULL)
FontCache::Default()->Recycle(fCacheEntry);
}
void SetTo(FontCacheEntry* entry)
{
ASSERT(entry != NULL);
ASSERT(fCacheEntry == NULL);
fCacheEntry = entry;
}
bool ReadLock()
{
ASSERT(fCacheEntry != NULL);
ASSERT(fWriteLocked == false);
if (fLocked)
return true;
if (!fCacheEntry->ReadLock()) {
_Cleanup();
return false;
}
fLocked = true;
return true;
}
bool WriteLock()
{
ASSERT(fCacheEntry != NULL);
if (fWriteLocked)
return true;
if (fLocked) {
if (!fCacheEntry->ReadUnlock()) {
_Cleanup();
return false;
}
}
if (!fCacheEntry->WriteLock()) {
_Cleanup();
return false;
}
fLocked = true;
fWriteLocked = true;
return true;
}
bool Unlock()
{
ASSERT(fCacheEntry != NULL);
if (!fLocked)
return true;
if (fWriteLocked) {
if (!fCacheEntry->WriteUnlock()) {
_Cleanup();
return false;
}
} else {
if (!fCacheEntry->ReadUnlock()) {
_Cleanup();
return false;
}
}
fLocked = false;
fWriteLocked = false;
return true;
}
bool SetFallback(FontCacheReference* fallback)
{
ASSERT(fCacheEntry != NULL);
ASSERT(fallback != NULL);
ASSERT(fallback->Entry() != NULL);
ASSERT(fallback->Entry() != fCacheEntry);
if (fFallbackReference == fallback)
return true;
if (fFallbackReference != NULL) {
fFallbackReference->Unlock();
fFallbackReference = NULL;
}
if (fallback->Entry() < fCacheEntry) {
if (fLocked && !Unlock())
return false;
if (!fallback->WriteLock()) {
WriteLock();
return false;
}
fFallbackReference = fallback;
return WriteLock();
}
if (fLocked && !fWriteLocked && !Unlock())
return false;
if (!WriteLock() || !fallback->WriteLock())
return false;
fFallbackReference = fallback;
return true;
}
inline FontCacheEntry* Entry() const
{
return fCacheEntry;
}
inline bool WriteLocked() const
{
return fWriteLocked;
}
private:
void _Cleanup()
{
if (fFallbackReference != NULL) {
fFallbackReference->Unlock();
fFallbackReference = NULL;
}
if (fCacheEntry != NULL)
FontCache::Default()->Recycle(fCacheEntry);
fCacheEntry = NULL;
fLocked = false;
fWriteLocked = false;
}
private:
FontCacheEntry* fCacheEntry;
FontCacheReference* fFallbackReference;
bool fLocked;
bool fWriteLocked;
};
class GlyphLayoutEngine {
public:
static bool IsWhiteSpace(uint32 glyphCode);
static FontCacheEntry* FontCacheEntryFor(const ServerFont& font,
bool forceVector);
template<class GlyphConsumer>
static bool LayoutGlyphs(GlyphConsumer& consumer,
const ServerFont& font,
const char* utf8String,
int32 length, int32 maxChars,
const escapement_delta* delta = NULL,
uint8 spacing = B_BITMAP_SPACING,
const BPoint* offsets = NULL,
FontCacheReference* cacheReference = NULL);
static void PopulateFallbacks(
BObjectList<FontCacheReference, true>& fallbacks,
const ServerFont& font, bool forceVector);
static FontCacheReference* GetFallbackReference(
BObjectList<FontCacheReference, true>& fallbacks,
uint32 charCode);
private:
static const GlyphCache* _CreateGlyph(
FontCacheReference& cacheReference,
BObjectList<FontCacheReference, true>& fallbacks,
const ServerFont& font, bool needsVector,
uint32 glyphCode);
GlyphLayoutEngine();
virtual ~GlyphLayoutEngine();
};
inline bool
GlyphLayoutEngine::IsWhiteSpace(uint32 charCode)
{
switch (charCode) {
case 0x0009:
case 0x000b:
case 0x000c:
case 0x0020:
case 0x00a0:
case 0x000a:
case 0x000d:
case 0x2028:
case 0x2029:
return true;
}
return false;
}
inline FontCacheEntry*
GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, bool forceVector)
{
FontCache* cache = FontCache::Default();
FontCacheEntry* entry = cache->FontCacheEntryFor(font, forceVector);
return entry;
}
template<class GlyphConsumer>
inline bool
GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
const ServerFont& font,
const char* utf8String, int32 length, int32 maxChars,
const escapement_delta* delta, uint8 spacing,
const BPoint* offsets, FontCacheReference* _cacheReference)
{
FontCacheEntry* entry = NULL;
FontCacheReference* pCacheReference;
FontCacheReference cacheReference;
BObjectList<FontCacheReference, true> fallbacksList(21);
if (_cacheReference != NULL) {
pCacheReference = _cacheReference;
entry = _cacheReference->Entry();
} else
pCacheReference = &cacheReference;
if (entry == NULL) {
entry = FontCacheEntryFor(font, consumer.NeedsVector());
if (entry == NULL)
return false;
pCacheReference->SetTo(entry);
if (!pCacheReference->ReadLock())
return false;
}
consumer.Start();
double x = 0.0;
double y = 0.0;
if (offsets) {
x = offsets[0].x;
y = offsets[0].y;
}
double advanceX = 0.0;
double advanceY = 0.0;
double size = font.Size();
uint32 lastCharCode = 0;
uint32 charCode;
int32 index = 0;
const char* start = utf8String;
while (maxChars-- > 0 && (charCode = UTF8ToCharCode(&utf8String)) != 0) {
if (offsets != NULL) {
x = offsets[index].x;
y = offsets[index].y;
} else {
if (spacing == B_STRING_SPACING)
entry->GetKerning(lastCharCode, charCode, &advanceX, &advanceY);
x += advanceX;
y += advanceY;
}
const GlyphCache* glyph = entry->CachedGlyph(charCode);
if (glyph == NULL) {
glyph = _CreateGlyph(*pCacheReference, fallbacksList, font,
consumer.NeedsVector(), charCode);
if (pCacheReference->Entry() == NULL)
return false;
}
if (glyph == NULL) {
consumer.ConsumeEmptyGlyph(index++, charCode, x, y);
advanceX = 0;
advanceY = 0;
} else {
if (spacing == B_CHAR_SPACING) {
advanceX = glyph->precise_advance_x * size;
advanceY = glyph->precise_advance_y * size;
} else {
advanceX = glyph->advance_x;
advanceY = glyph->advance_y;
}
if (delta != NULL) {
advanceX += IsWhiteSpace(charCode)
? delta->space : delta->nonspace;
}
if (!consumer.ConsumeGlyph(index++, charCode, glyph, entry, x, y,
advanceX, advanceY)) {
advanceX = 0.0;
advanceY = 0.0;
break;
}
}
lastCharCode = charCode;
if (utf8String - start + 1 > length)
break;
}
x += advanceX;
y += advanceY;
consumer.Finish(x, y);
return true;
}
inline const GlyphCache*
GlyphLayoutEngine::_CreateGlyph(FontCacheReference& cacheReference,
BObjectList<FontCacheReference, true>& fallbacks,
const ServerFont& font, bool forceVector, uint32 charCode)
{
FontCacheEntry* entry = cacheReference.Entry();
if (entry->CanCreateGlyph(charCode)) {
if (cacheReference.WriteLock())
return entry->CreateGlyph(charCode);
return NULL;
}
if (fallbacks.IsEmpty())
PopulateFallbacks(fallbacks, font, forceVector);
FontCacheReference* fallbackReference = GetFallbackReference(fallbacks, charCode);
if (fallbackReference != NULL) {
if (cacheReference.SetFallback(fallbackReference))
return entry->CreateGlyph(charCode, fallbackReference->Entry());
if (cacheReference.Entry() == NULL)
return NULL;
}
if (cacheReference.WriteLock())
return entry->CreateGlyph(charCode);
return NULL;
}
inline void
GlyphLayoutEngine::PopulateFallbacks(
BObjectList<FontCacheReference, true>& fallbacksList,
const ServerFont& font, bool forceVector)
{
ASSERT(fallbacksList.IsEmpty());
static const char* fallbacks[] = {
"Noto Sans",
"Noto Sans Thai",
"Noto Sans CJK JP",
"Noto Sans Cherokee",
"Noto Sans Symbols",
"Noto Sans Symbols 2",
"Noto Emoji",
};
if (!gFontManager->Lock())
return;
static const int nFallbacks = B_COUNT_OF(fallbacks);
static const int acceptAnyStyle = 2;
for (int degradeLevel = 0; degradeLevel <= acceptAnyStyle; degradeLevel++) {
const char* fontStyle;
if (degradeLevel == 0)
fontStyle = font.Style();
else if (degradeLevel == 1)
fontStyle = "Regular";
else
fontStyle = NULL;
for (int i = 0; i < nFallbacks; i++) {
FontStyle* fallbackStyle;
if (degradeLevel != acceptAnyStyle) {
fallbackStyle = gFontManager->GetStyle(fallbacks[i], fontStyle);
} else {
fallbackStyle = gFontManager->GetStyleByIndex(fallbacks[i], 0);
}
if (fallbackStyle == NULL)
continue;
ServerFont fallbackFont(*fallbackStyle, font.Size());
FontCacheEntry* entry = FontCacheEntryFor(fallbackFont, forceVector);
if (entry == NULL)
continue;
FontCacheReference* cacheReference = new(std::nothrow) FontCacheReference();
if (cacheReference != NULL) {
cacheReference->SetTo(entry);
fallbacksList.AddItem(cacheReference);
} else
FontCache::Default()->Recycle(entry);
}
}
gFontManager->Unlock();
}
inline FontCacheReference*
GlyphLayoutEngine::GetFallbackReference(
BObjectList<FontCacheReference, true>& fallbacks, uint32 charCode)
{
int32 count = fallbacks.CountItems();
for (int32 index = 0; index < count; index++) {
FontCacheReference* fallbackReference = fallbacks.ItemAt(index);
FontCacheEntry* fallbackEntry = fallbackReference->Entry();
if (fallbackEntry != NULL && fallbackEntry->CanCreateGlyph(charCode))
return fallbackReference;
}
return NULL;
}
#endif