/* * This file is part of OpenTTD. * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ /** @file gfx_layout.h Functions related to laying out the texts. */ #ifndef GFX_LAYOUT_H #define GFX_LAYOUT_H #include "misc/lrucache.hpp" #include "fontcache.h" #include "gfx_func.h" #include "core/math_func.hpp" #include /** * Text drawing parameters, which can change while drawing a line, but are kept between multiple parts * of the same text, e.g. on line breaks. */ struct FontState { FontSize fontsize; ///< Current font size. TextColour cur_colour; ///< Current text colour. std::vector colour_stack; ///< Stack of colours to assist with colour switching. FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {} FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {} auto operator<=>(const FontState &) const = default; /** * Switch to new colour \a c. * @param c New colour to use. */ inline void SetColour(TextColour c) { assert(((c & TC_COLOUR_MASK) >= TC_BLUE && (c & TC_COLOUR_MASK) <= TC_BLACK) || (c & TC_COLOUR_MASK) == TC_INVALID); assert((c & (TC_COLOUR_MASK | TC_FLAGS_MASK)) == c); if ((this->cur_colour & TC_FORCED) == 0) this->cur_colour = c; } /** * Switch to and pop the last saved colour on the stack. */ inline void PopColour() { if (colour_stack.empty()) return; SetColour(colour_stack.back()); colour_stack.pop_back(); } /** * Push the current colour on to the stack. */ inline void PushColour() { colour_stack.push_back(this->cur_colour); } /** * Switch to using a new font \a f. * @param f New font to use. */ inline void SetFontSize(FontSize f) { this->fontsize = f; } }; template struct std::hash> { size_t operator()(const std::vector &vec) const { /* This is not an optimal hash algorithm but in most cases this is empty and therefore the same anyway. */ return std::transform_reduce(std::begin(vec), std::end(vec), std::hash{}(std::size(vec)), [](const size_t &a, const size_t &b) -> size_t { return a ^ b; }, [](const T &x) -> size_t { return std::hash{}(x); }); } }; template <> struct std::hash { std::size_t operator()(const FontState &state) const noexcept { size_t h1 = std::hash{}(state.fontsize); size_t h2 = std::hash{}(state.cur_colour); size_t h3 = std::hash>{}(state.colour_stack); return h1 ^ (h2 << 1) ^ (h3 << 2); } }; /** * Container with information about a font. */ class Font { public: FontCache *fc; ///< The font we are using. TextColour colour; ///< The colour this font has to be. Font(FontSize size, TextColour colour); }; /** Mapping from index to font. The pointer is owned by FontColourMap. */ using FontMap = std::vector>; /** * Interface to glue fallback and normal layouter into one. */ class ParagraphLayouter { public: virtual ~ParagraphLayouter() = default; /** Position of a glyph within a VisualRun. */ class Position { public: int16_t left; ///< Left-most position of glyph. int16_t right; ///< Right-most position of glyph. int16_t top; ///< Top-most position of glyph. constexpr inline Position(int16_t left, int16_t right, int16_t top) : left(left), right(right), top(top) { } /** Conversion from a single point to a Position. */ constexpr inline Position(const Point &pt) : left(pt.x), right(pt.x), top(pt.y) { } }; /** Visual run contains data about the bit of text with the same font. */ class VisualRun { public: virtual ~VisualRun() = default; virtual const Font *GetFont() const = 0; virtual int GetGlyphCount() const = 0; virtual std::span GetGlyphs() const = 0; virtual std::span GetPositions() const = 0; virtual int GetLeading() const = 0; virtual std::span GetGlyphToCharMap() const = 0; }; /** A single line worth of VisualRuns. */ class Line { public: virtual ~Line() = default; virtual int GetLeading() const = 0; virtual int GetWidth() const = 0; virtual int CountRuns() const = 0; virtual const VisualRun &GetVisualRun(int run) const = 0; virtual int GetInternalCharLength(char32_t c) const = 0; }; virtual void Reflow() = 0; virtual std::unique_ptr NextLine(int max_width) = 0; }; /** * The layouter performs all the layout work. * * It also accounts for the memory allocations and frees. */ class Layouter : public std::vector { std::string_view string; ///< Pointer to the original string. /** Key into the linecache */ struct LineCacheKey { FontState state_before; ///< Font state at the beginning of the line. std::string str; ///< Source string of the line (including colour and font size codes). }; struct LineCacheQuery { const FontState &state_before; ///< Font state at the beginning of the line. std::string_view str; ///< Source string of the line (including colour and font size codes). }; friend struct std::hash; struct LineCacheHash; struct LineCacheEqualTo { using is_transparent = void; template bool operator()(const Tlhs &lhs, const Trhs &rhs) const { return lhs.state_before == rhs.state_before && lhs.str == rhs.str; } }; public: /** Item in the linecache */ struct LineCacheItem { /* Due to the type of data in the buffer differing depending on the Layouter, we need to pass our own deleter routine. */ using Buffer = std::unique_ptr; /* Stuff that cannot be freed until the ParagraphLayout is freed */ Buffer buffer{nullptr, [](void *){}}; ///< Accessed by our ParagraphLayout::nextLine. FontMap runs; ///< Accessed by our ParagraphLayout::nextLine. FontState state_after; ///< Font state after the line. std::unique_ptr layout = nullptr; ///< Layout of the line. std::vector> cached_layout{}; ///< Cached results of line layouting. int cached_width = 0; ///< Width used for the cached layout. }; private: using LineCache = LRUCache; static std::unique_ptr linecache; static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state); using FontColourMap = std::map>; static FontColourMap fonts[FS_END]; public: static Font *GetFont(FontSize size, TextColour colour); Layouter(std::string_view str, int maxw = INT32_MAX, FontSize fontsize = FS_NORMAL); Dimension GetBounds(); ParagraphLayouter::Position GetCharPosition(std::string_view::const_iterator ch) const; ptrdiff_t GetCharAtPosition(int x, size_t line_index) const; static void Initialize(); static void ResetFontCache(FontSize size); static void ResetLineCache(); }; ParagraphLayouter::Position GetCharPosInString(std::string_view str, size_t pos, FontSize start_fontsize = FS_NORMAL); ptrdiff_t GetCharAtPosition(std::string_view str, int x, FontSize start_fontsize = FS_NORMAL); template <> struct std::hash { std::size_t operator()(const Layouter::LineCacheQuery &state) const noexcept { size_t h1 = std::hash{}(state.str); size_t h2 = std::hash{}(state.state_before); return h1 ^ (h2 << 1); } }; struct Layouter::LineCacheHash { using is_transparent = void; std::size_t operator()(const Layouter::LineCacheKey &query) const { return std::hash{}(LineCacheQuery{query.state_before, query.str}); } std::size_t operator()(const Layouter::LineCacheQuery &query) const { return std::hash{}(query); } }; #endif /* GFX_LAYOUT_H */