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