1 /* 2 Copyright (c) 2010 Peter "Corsix" Cawley 3 4 Permission is hereby granted, free of charge, to any person obtaining a copy of 5 this software and associated documentation files (the "Software"), to deal in 6 the Software without restriction, including without limitation the rights to 7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 of the Software, and to permit persons to whom the Software is furnished to do 9 so, subject to the following conditions: 10 11 The above copyright notice and this permission notice shall be included in all 12 copies or substantial portions of the Software. 13 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 SOFTWARE. 21 */ 22 23 #ifndef CORSIX_TH_TH_GFX_FONT_H_ 24 #define CORSIX_TH_TH_GFX_FONT_H_ 25 #include "th_gfx.h" 26 #ifdef CORSIX_TH_USE_FREETYPE2 27 #include <ft2build.h> 28 #include FT_FREETYPE_H 29 #endif 30 31 class render_target; 32 33 class sprite_sheet; 34 35 enum class text_alignment { 36 left = 0, 37 center = 1, 38 right = 2, 39 }; 40 41 /** Structure for the bounds of a text string that is rendered to the screen. */ 42 struct text_layout { 43 //! Number of rows the rendered text spans 44 int row_count; 45 46 //! Left X-coordinate for the start of the text 47 int start_x; 48 49 //! Right X-coordinate for the right part of the last letter rendered 50 int end_x; 51 52 //! Top Y-coordinate for the start of the text 53 int start_y; 54 55 //! Bottom Y-coordinate for the end of the text 56 int end_y; 57 58 //! Width of the widest line in the text 59 int width; 60 }; 61 62 class font { 63 public: 64 virtual ~font() = default; 65 66 //! Get the size of drawn text. 67 /*! 68 If iMaxWidth is specified the text will wrap, so that the height can 69 span multiple rows. Otherwise gets the size of a single line of text. 70 @param sMessage A UTF-8 encoded string containing a single line of text 71 to measure the width and height of. 72 @param iMessageLength The length, in bytes (not characters), of the 73 string at sMessage. 74 @param iMaxWidth The maximum length, in pixels, that the text may 75 occupy. Default is INT_MAX. 76 */ 77 virtual text_layout get_text_dimensions(const char* sMessage, 78 size_t iMessageLength, 79 int iMaxWidth = INT_MAX) const = 0; 80 81 //! Draw a single line of text 82 /*! 83 @param pCanvas The render target to draw onto. 84 @param sMessage A UTF-8 encoded string containing a single line of text 85 to draw. 86 @param iMessageLength The length, in bytes (not characters), of the 87 string at sMessage. 88 @param iX The X coordinate of the top-left corner of the bounding 89 rectangle for the drawn text. 90 @param iY The Y coordinate of the top-left corner of the bounding 91 rectangle for the drawn text. 92 */ 93 virtual void draw_text(render_target* pCanvas, const char* sMessage, 94 size_t iMessageLength, int iX, int iY) const = 0; 95 96 //! Draw a single line of text, splitting it at word boundaries 97 /*! 98 This function still only draws a single line of text (i.e. any line 99 breaks like `\r` and `\n` in sMessage are ignored), but inserts line 100 breaks between words so that no single line is wider than iWidth pixels. 101 If iMaxRows is specified it will simply cut after that many rows. 102 @param pCanvas The canvas on which to draw. Can be nullptr, in which 103 case nothing is drawn, but other calculations are still made. 104 @param sMessage The line of text to draw, encoded in CP437. 105 @param iMessageLength The length (in bytes) of sMessage. 106 @param iX The X position to start drawing on the canvas. 107 @param iY The Y position to start drawing on the canvas. 108 @param iWidth The maximum width of each line of text. 109 @param iMaxRows The maximum number of rows to draw. Default is INT_MAX. 110 @param iSkipRows Start rendering text after skipping this many rows. 111 @param eAlign How to align each line of text if the width of the line 112 of text is smaller than iWidth. 113 */ 114 virtual text_layout draw_text_wrapped( 115 render_target* pCanvas, const char* sMessage, size_t iMessageLength, 116 int iX, int iY, int iWidth, int iMaxRows = INT_MAX, int iSkipRows = 0, 117 text_alignment eAlign = text_alignment::left) const = 0; 118 }; 119 120 class bitmap_font final : public font { 121 public: 122 bitmap_font(); 123 124 //! Set the character glyph sprite sheet 125 /*! 126 The sprite sheet should have the space character (ASCII 0x20) at sprite 127 index 1, and other ASCII characters following on in simple order (i.e. 128 '!' (ASCII 0x21) at index 2, 'A' (ASCII 0x41) at index 34, etc.) 129 */ 130 void set_sprite_sheet(sprite_sheet* pSpriteSheet); 131 get_sprite_sheet()132 sprite_sheet* get_sprite_sheet() { return sheet; } 133 134 //! Set the separation between characters and between lines 135 /*! 136 Generally, the sprite sheet glyphs will already include separation, and 137 thus no extra separation is required (set iCharSep and iLineSep to 0). 138 */ 139 void set_separation(int iCharSep, int iLineSep); 140 141 text_layout get_text_dimensions(const char* sMessage, size_t iMessageLength, 142 int iMaxWidth = INT_MAX) const override; 143 144 void draw_text(render_target* pCanvas, const char* sMessage, 145 size_t iMessageLength, int iX, int iY) const override; 146 147 text_layout draw_text_wrapped( 148 render_target* pCanvas, const char* sMessage, size_t iMessageLength, 149 int iX, int iY, int iWidth, int iMaxRows = INT_MAX, int iSkipRows = 0, 150 text_alignment eAlign = text_alignment::left) const override; 151 152 private: 153 sprite_sheet* sheet; 154 int letter_spacing; 155 int line_spacing; 156 }; 157 158 #ifdef CORSIX_TH_USE_FREETYPE2 159 //! Adaptor around the FreeType2 library to a THFont. 160 /*! 161 Due to the relatively high cost of rendering a message with FreeType, this 162 class implements internal caching of messages, so rendering a message once 163 will be quite expensive, but subsequently rendering the same message again 164 will be quite cheap (provided that it hasn't fallen out of the cache). 165 166 Unlike THBitmapFont which sits entirely on top of existing interfaces, some 167 of the internal methods of this class are implemented by each individual 168 rendering engine (said methods are roughly for the equivalent of the 169 THRawBitmap class, but with an alpha channel, and a single colour rather 170 than a palette). 171 */ 172 class freetype_font final : public font { 173 public: 174 freetype_font(); 175 ~freetype_font() override; 176 177 //! Get the copyright notice which should be displayed for FreeType2. 178 /*! 179 To comply with the FreeType2 license, the string returned by this 180 function needs to be displayed at some point. 181 @return A null-terminated UTF-8 encoded string. 182 */ 183 static const char* get_copyright_notice(); 184 185 //! Initialise the FreeType2 library. 186 /*! 187 This will be called automatically by setFace() as required. 188 */ 189 FT_Error initialise(); 190 191 //! Remove all cached strings, as our graphics context has changed 192 void clear_cache(); 193 194 //! Set the font face to be used. 195 /*! 196 @param pData Pointer to the start of a font file loaded into memory. 197 This block of memory must remain valid for at least the lifetime 198 of the THFreeTypeFont objcect. 199 @param iLength The size, in bytes, of the font file at pData. 200 */ 201 FT_Error set_face(const uint8_t* pData, size_t iLength); 202 203 //! Set the font size and colour to match that of a bitmap font. 204 /*! 205 Note that the matching is done on a best-effort basis, and will likely 206 not be perfect. This must be called after setFace(). 207 208 @param pBitmapFontSpriteSheet The sprite sheet of the bitmap font. 209 */ 210 FT_Error match_bitmap_font(sprite_sheet* pBitmapFontSpriteSheet); 211 212 //! Set the ideal character size using pixel values. 213 /*! 214 Note that the given size might be changed a small amount if doing so 215 would result in a much nicer rendered font. This must be called after 216 setFace(). 217 */ 218 FT_Error set_ideal_character_size(int iWidth, int iHeight); 219 220 text_layout get_text_dimensions(const char* sMessage, size_t iMessageLength, 221 int iMaxWidth = INT_MAX) const override; 222 223 void draw_text(render_target* pCanvas, const char* sMessage, 224 size_t iMessageLength, int iX, int iY) const override; 225 226 text_layout draw_text_wrapped( 227 render_target* pCanvas, const char* sMessage, size_t iMessageLength, 228 int iX, int iY, int iWidth, int iMaxRows = INT_MAX, int iSkipRows = 0, 229 text_alignment eAlign = text_alignment::left) const override; 230 231 private: 232 struct cached_text { 233 //! The text being converted to pixels 234 char* message; 235 236 //! Raw pixel data in row major 8-bit greyscale 237 uint8_t* data; 238 239 //! Generated texture ready to be rendered 240 SDL_Texture* texture; 241 242 //! The length of sMessage 243 size_t message_length; 244 245 //! The size of the buffer allocated to store sMessage 246 size_t message_buffer_length; 247 248 //! Width of the image to draw 249 int width; 250 251 //! Height of the image to draw 252 int height; 253 254 //! The width of the longest line of text in in the textbox in pixels 255 int widest_line_width; 256 257 //! X Coordinate trailing the last character in canvas coordinates 258 int last_x; 259 260 //! Number of rows required 261 int row_count; 262 263 //! Alignment of the message in the box 264 text_alignment alignment; 265 266 //! True when the data reflects the message given the size constraints 267 bool is_valid; 268 }; 269 270 //! Render a FreeType2 monochrome bitmap to a cache canvas. 271 void render_mono(cached_text* pCacheEntry, FT_Bitmap* pBitmap, FT_Pos x, 272 FT_Pos y) const; 273 274 //! Render a FreeType2 grayscale bitmap to a cache canvas. 275 void render_gray(cached_text* pCacheEntry, FT_Bitmap* pBitmap, FT_Pos x, 276 FT_Pos y) const; 277 278 static FT_Library freetype_library; 279 static int freetype_init_count; 280 static const int cache_size_log2 = 7; 281 FT_Face font_face; 282 argb_colour colour; 283 bool is_done_freetype_init; 284 mutable cached_text cache[1 << cache_size_log2]; 285 286 // The following five methods are implemented by the rendering engine. 287 288 //! Query if 1-bit monochrome or 8-bit grayscale rendering should be used. 289 /*! 290 @return true if 1-bit monochrome rendering should be used, false if 291 8-bit grayscale rendering should be used (though in the latter 292 case, 1-bit rendering might still get used). 293 */ 294 bool is_monochrome() const; 295 296 //! Convert a cache canvas containing rendered text into a texture. 297 /*! 298 @param pEventualCanvas A pointer to the rendertarget we'll be using to 299 draw this. 300 @param pCacheEntry A cache entry whose pData field points to a pixmap 301 of size iWidth by iHeight. This method will convert said pixmap to 302 an object which can be used by the rendering engine, and store the 303 result in the pTexture or iTexture field. 304 */ 305 void make_texture(render_target* pEventualCanvas, 306 cached_text* pCacheEntry) const; 307 308 //! Free a previously-made texture of a cache entry. 309 /*! 310 This call should free all the resources previously allocated by a call 311 to _makeTexture() and set the texture field to indicate no texture. 312 313 @param pCacheEntry A cache entry previously passed to _makeTexture(). 314 */ 315 void free_texture(cached_text* pCacheEntry) const; 316 317 //! Render a previously-made texture of a cache entry. 318 /*! 319 @param pCanvas The canvas on which to draw. 320 @param pCacheEntry A cache entry containing the texture to draw, which 321 will have been stored in the pTexture or iTexture field by a prior 322 call to _makeTexture(). 323 @param iX The X position at which to draw the texture on the canvas. 324 @param iY The Y position at which to draw the texture on the canvas. 325 */ 326 void draw_texture(render_target* pCanvas, cached_text* pCacheEntry, int iX, 327 int iY) const; 328 }; 329 #endif // CORSIX_TH_USE_FREETYPE2 330 331 #endif // CORSIX_TH_TH_GFX_FONT_H_ 332