guitextwrapper.h File Reference

Go to the documentation of this file. Source: include/ffw/gui/guitextwrapper.h

/* This file is part of FineFramework project */
#ifndef FFW_UTILS_TEXT_WRAPPER
#define FFW_UTILS_TEXT_WRAPPER

#include <cstdint>
#include "guifont.h"
#include "third_party/utfcpp/checked.h"

namespace ffw {
    template<class T>
    class GuiTextWrapper {
    public:
        struct Token {
            Token(const T* str, size_t len, float width):str(str),len(len),width(width){

            }

            Token():str(nullptr),len(0),width(0.0f){

            }

            const T* str;
            size_t len;
            float width;

            operator bool() const {
                return str != nullptr;
            }
        };
        GuiTextWrapper(const T* start, const T* end):
            start(start),end(end) {
            
        }
        Token next(const GuiFont* font, const float maxWidth) {
            auto width = 0.0f;
            const T* from = start;
            const T* lastChar = nullptr;
            const T* opportunityPtr = nullptr;
            const T* startOfWord = nullptr;
            auto previousWasWhitespace = false;
            auto opportunityWidth = 0.0f;
            auto whitespaceWidth = 0.0f;

            while(start != end) {
                try {
                    auto isNewline = false;
                    auto isWhitespace = false;
                    const T* currentPtr = start;
                    const auto chr = goto_next(start, end);
                    const auto chrWidth = font->getCharAdvance(chr);

                    switch(chr) {
                        case 9:  // \t
                        case 11: // \v
                        case 12: // \f
                        case 32: // space
                        case 13: // \r
                        case 0x00a0: // NBSP
                            whitespaceWidth = width;
                            isWhitespace = true;
                            break;
                        case 0x0085: // NEL
                        case 10: // \n
                            whitespaceWidth = width;
                            isWhitespace = true;
                            isNewline = true;
                            break;
                        case '-':
                        case '.':
                        case '_':
                        case '=':
                        case '+':
                        case '%':
                        case '>':
                        case ')':
                        case '}':
                        case ']':
                        case ',':
                        case ':':
                        case ';':
                        case '\"':
                        case '*':
                        case '/':
                        case '?':
                        case '!':
                            opportunityPtr = currentPtr;
                            opportunityWidth = width;
                            break;
                        default:
                            break;
                    }

                    if (isNewline) {
                        if (lastChar != nullptr) {
                            start = currentPtr;
                            return Token(from, size_t(getNextChar(lastChar) - from), width);
                        } else {
                            // Peek forward and check if we have \r character
                            if (start != end) {
                                const auto test = peek_next(start, end);
                                if (test == '\r') {
                                    ++start;
                                }
                            }
                            if (currentPtr == from) {
                                return Token(currentPtr, 0, 0.0f);
                            } else {
                                start = currentPtr;
                                return Token(from, currentPtr - from, width);
                            }
                        }
                    }

                    if(!isWhitespace){
                        if(previousWasWhitespace) {
                            startOfWord = currentPtr;
                        }
                        lastChar = currentPtr;
                    }

                    if (maxWidth > 0.0f && width + chrWidth > maxWidth) {
                        if(lastChar != nullptr && !isWhitespace) {
                            if(opportunityPtr != nullptr){
                                if (startOfWord != nullptr && startOfWord > opportunityPtr) {
                                    start = startOfWord;
                                    return Token(from, size_t(start - from), whitespaceWidth + chrWidth); 
                                }
                                if(lastChar != opportunityPtr) {
                                    // A special case where there is a trailing whitespace at the end of the word
                                    start = getNextChar(opportunityPtr);
                                    return Token(from, size_t(start - from), opportunityWidth + chrWidth);
                                }
                            }
                            if(startOfWord != nullptr) {
                                start = startOfWord;
                                return Token(from, size_t(start - from), whitespaceWidth + chrWidth);
                            }
                            start = currentPtr;
                            return Token(from, size_t(currentPtr - from), width);
                        }
                    }

                    if(!isWhitespace){
                        previousWasWhitespace = false;
                    } else {
                        previousWasWhitespace = true;
                    }

                    if(!isNewline && chr != '\r'){
                      width += chrWidth;
                    }
                } catch (utf8::not_enough_room& e){
                    (void)e;
                    return Token();
                } catch (utf8::invalid_utf8& e) {
                    (void)e;
                    return Token();
                }
            }

            if (from != end) {
                return Token(from, size_t(end - from), width);
            }
            return Token();
        }
    private:
        inline const T* getNextChar(const T* s) const {
            if(s + 1 == end) {
                return end;
            }
            advance(s, 1, end);
            return s;
        }
        static inline void advance(const T*& it, size_t n, const T* end);
        static inline uint32_t peek_next(const T* it, const T* end);
        static inline uint32_t goto_next(const T*& it, const T* end);
        const T* start;
        const T* end;
    };

    template<>
    inline void GuiTextWrapper<char>::advance(const char*& it, size_t n, const char* end) {
        utf8::advance(it, n, end);
    }

    template<>
    inline void GuiTextWrapper<wchar_t>::advance(const wchar_t*& it, size_t n, const wchar_t* end) {
        it += n;
    }

    template<>
    inline uint32_t GuiTextWrapper<char>::peek_next(const char* it, const char* end) {
        return utf8::peek_next(it, end);
    }

    template<>
    inline uint32_t GuiTextWrapper<wchar_t>::peek_next(const wchar_t* it,  const wchar_t* end) {
        return *(it + 1);
    }

    template<>
    inline uint32_t GuiTextWrapper<char>::goto_next(const char*& it, const char* end) {
        return utf8::next(it, end);
    }

    template<>
    inline uint32_t GuiTextWrapper<wchar_t>::goto_next(const wchar_t*& it, const wchar_t* end) {
        (void)end;
        return *(it++);
    }
}

#endif