root/src/servers/app/font/GlyphLayoutEngine.h
/*
 * 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;
                }

                // 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.
                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:    /* tab */
                case 0x000b:    /* vertical tab */
                case 0x000c:    /* form feed */
                case 0x0020:    /* space */
                case 0x00a0:    /* non breaking space */
                case 0x000a:    /* line feed */
                case 0x000d:    /* carriage return */
                case 0x2028:    /* line separator */
                case 0x2029:    /* paragraph separator */
                        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)
{
        // TODO: implement spacing modes
        FontCacheEntry* entry = NULL;
        FontCacheReference* pCacheReference;
        FontCacheReference cacheReference;
        BObjectList<FontCacheReference, true> fallbacksList(21);

        if (_cacheReference != NULL) {
                pCacheReference = _cacheReference;
                entry = _cacheReference->Entry();
                // When there is already a cacheReference, it means there was already
                // an iteration over the glyphs. The use-case is for example to do
                // a layout pass to get the string width for the bounding box, then a
                // second layout pass to actually render the glyphs to the screen.
                // This means that the fallback entry mechanism will not do any good
                // for the second pass, since the fallback glyphs have been stored in
                // the original 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;
        } // else the entry was already used and is still locked

        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; // Needed for kerning in B_STRING_SPACING mode
        uint32 charCode;
        int32 index = 0;
        const char* start = utf8String;
        while (maxChars-- > 0 && (charCode = UTF8ToCharCode(&utf8String)) != 0) {

                if (offsets != NULL) {
                        // Use direct glyph locations instead of calculating them
                        // from the advance values
                        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);

                        // Something may have gone wrong while reacquiring the entry lock
                        if (pCacheReference->Entry() == NULL)
                                return false;
                }

                if (glyph == NULL) {
                        consumer.ConsumeEmptyGlyph(index++, charCode, x, y);
                        advanceX = 0;
                        advanceY = 0;
                } else {
                        // get next increment for pen position
                        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;
                        }

                        // adjust for custom spacing
                        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();

        // Avoid loading the fallbacks if our font can create the glyph.
        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;
        }

        // No one knows how to draw this, so use the missing glyph symbol.
        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());

        // 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
        // and b) be similar to the original font. So there should be a mapping
        // of some kind to know the most suitable fallback font.
        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 {
                                // At this point we'll just take whatever we are given
                                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 // GLYPH_LAYOUT_ENGINE_H