1 /++
2  + Authors: Avaxar <avaxar@nekkl.org>
3  + Copyright: Copyright © 2023, Avaxar
4  + License: $(LINK2 https://mit-license.org, MIT License)
5  +/
6 
7 module dsdl2.ttf;
8 @safe:
9 
10 // dfmt off
11 import bindbc.sdl;
12 static if (bindSDLTTF):
13 // dfmt on
14 
15 import dsdl2.sdl;
16 import dsdl2.pixels : Color;
17 import dsdl2.surface;
18 
19 import core.memory : GC;
20 import std.conv : to;
21 import std.format : format;
22 import std.string : toStringz;
23 import std.typecons : Tuple;
24 
25 version (BindSDL_Static) {
26 }
27 else {
28     /++
29      + Loads the SDL2_ttf shared dynamic library, which wraps bindbc-sdl's `loadSDLTTF` function
30      +
31      + Unless if bindbc-sdl is on static mode (by adding a `BindSDL_Static` version), this function will exist and must
32      + be called before any calls are made to the library. Otherwise, a segfault will happen upon any function calls.
33      +
34      + Params:
35      +   libName = name or path to look the SDL2_ttf SO/DLL for, otherwise `null` for default searching path
36      + Throws: `dsdl2.SDLException` if failed to find the library
37      +/
38     void loadSO(string libName = null) @trusted {
39         SDLTTFSupport current = libName is null ? loadSDLTTF() : loadSDLTTF(libName.toStringz());
40         if (current == sdlTTFSupport) {
41             return;
42         }
43 
44         Version wanted = Version(sdlTTFSupport);
45         if (current == SDLTTFSupport.badLibrary) {
46             import std.stdio : writeln;
47 
48             writeln("WARNING: dsdl2 expects SDL_ttf ", wanted.format(), ", but got ", getVersion().format(), ".");
49         }
50         else if (current == SDLTTFSupport.noLibrary) {
51             throw new SDLException("No SDL2_ttf library found, especially of version " ~ wanted.format(),
52                 __FILE__, __LINE__);
53         }
54     }
55 }
56 
57 /++
58  + Wraps `TTF_Init` which initializes SDL2_ttf
59  +
60  + Throws: `dsdl2.SDLException` if failed to initialize
61  + Example:
62  + ---
63  + dsdl2.ttf.init();
64  + ---
65  +/
66 void init() @trusted {
67     if (TTF_Init() != 0) {
68         throw new SDLException;
69     }
70 }
71 
72 version (unittest) {
73     static this() {
74         version (BindSDL_Static) {
75         }
76         else {
77             dsdl2.ttf.loadSO();
78         }
79 
80         dsdl2.ttf.init();
81     }
82 }
83 
84 /++
85  + Wraps `TTF_Quit` which deinitializes SDL2_ttf
86  +/
87 void quit() @trusted {
88     TTF_Quit();
89 }
90 
91 /++
92  + Wraps `TTF_WasInit` which checks whether SDL2_ttf has been initialized
93  +
94  + Returns: `true` if the library has been initialized, otherwise `false`
95  +/
96 bool wasInit() @trusted {
97     return TTF_WasInit() > 0;
98 }
99 
100 /++
101  + Wraps `TTF_Linked_version` which gets the version of the linked SDL2_ttf library
102  +
103  + Returns: `dsdl2.Version` of the linked SDL2_ttf library
104  +/
105 Version getVersion() @trusted {
106     return Version(*TTF_Linked_Version());
107 }
108 
109 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
110     /++
111      + Wraps `TTF_GetFreeTypeVersion` (from SDL_ttf 2.0.18) which gets the version of the FreeType library used by the
112      + linked SDL2_ttf
113      +
114      + Returns: `dsdl2.Version` of the used FreeType library
115      +/
116     Version getFreeTypeVersion() @trusted
117     in {
118         assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
119     }
120     do {
121         int[3] ver = void;
122         TTF_GetFreeTypeVersion(&ver[0], &ver[1], &ver[2]);
123         return Version(ver[0].to!ubyte, ver[1].to!ubyte, ver[2].to!ubyte);
124     }
125 
126     /++
127      + Wraps `TTF_GetHarfBuzzVersion` (from SDL_ttf 2.0.18) which gets the version of the HarfBuzz library used by the
128      + linked SDL2_ttf
129      +
130      + Returns: `dsdl2.Version` of the used HarfBuzz library
131      +/
132     Version getHarfBuzzVersion() @trusted
133     in {
134         assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
135     }
136     do {
137         int[3] ver = void;
138         TTF_GetHarfBuzzVersion(&ver[0], &ver[1], &ver[2]);
139         return Version(ver[0].to!ubyte, ver[1].to!ubyte, ver[2].to!ubyte);
140     }
141 }
142 
143 /++
144  + D struct that wraps a glyph's metrics information
145  +/
146 struct GlyphMetrics {
147     int[2] min; /// Tuple of the `minX` and `minY` values
148     int[2] max; /// Tuple of the `maxX` and `maxY` values
149     int advance; /// Advancing step size of the glyph
150 
151     this() @disable;
152 
153     /++
154      + Constructs a new `dsdl2.ttf.GlyphMetrics` by feeding in its attributes
155      +
156      + Params:
157      +   min = tuple of the `minX` and `minY` values
158      +   max = tuple of the `maxX` and `maxY` values
159      +   advance = advancing step size of the glyph
160      +/
161     this(int[2] min, int[2] max, int advance) {
162         this.min = min;
163         this.max = max;
164         this.advance = advance;
165     }
166 
167     /++
168      + Formats the `dsdl2.ttf.GlyphMetrics` into its construction representation:
169      + `"dsdl2.ttf.GlyphMetrics(<min>, <max>, <advance>)"`
170      +
171      + Returns: the formatted `string`
172      +/
173     string toString() const {
174         return "dsdl2.ttf.GlyphMetrics(%s, %s, %d)".format(this.min, this.max, this.advance);
175     }
176 
177     /++
178      + Proxy to the minimum X value of the `dsdl2.ttf.GlyphMetrics`
179      +
180      + Returns: minimum X value of the `dsdl2.ttf.GlyphMetrics`
181      +/
182     ref inout(int) minX() return inout @property {
183         return this.min[0];
184     }
185 
186     /++
187      + Proxy to the minimum Y value of the `dsdl2.ttf.GlyphMetrics`
188      +
189      + Returns: minimum Y value of the `dsdl2.ttf.GlyphMetrics`
190      +/
191     ref inout(int) minY() return inout @property {
192         return this.min[1];
193     }
194 
195     /++
196      + Proxy to the maximum X value of the `dsdl2.ttf.GlyphMetrics`
197      +
198      + Returns: maximum X value of the `dsdl2.ttf.GlyphMetrics`
199      +/
200     ref inout(int) maxX() return inout @property {
201         return this.max[0];
202     }
203 
204     /++
205      + Proxy to the maximum Y value of the `dsdl2.ttf.GlyphMetrics`
206      +
207      + Returns: maximum Y value of the `dsdl2.ttf.GlyphMetrics`
208      +/
209     ref inout(int) maxY() return inout @property {
210         return this.max[1];
211     }
212 }
213 
214 /++
215  + D enum that wraps `TTF_HINTING_*` enumerations
216  +/
217 enum Hinting {
218     /++
219      + Wraps `TTF_HINTING_*` enumeration constants
220      +/
221     normal = TTF_HINTING_NORMAL,
222     light = TTF_HINTING_LIGHT, /// ditto
223     mono = TTF_HINTING_MONO, /// ditto
224     none = TTF_HINTING_NONE, /// ditto
225 
226     lightSubpixel = 4 /// Wraps `TTF_HINTING_LIGHT_SUBPIXEL` (from SDL_ttf 2.0.18)
227 }
228 
229 /++
230  + D enum that defines the render quality of font texts
231  +/
232 enum RenderQuality {
233     solid, /// Fast quality to 8-bit surface
234     shaded, /// High quality to 8-bit surface with background color
235     blended, /// High quality to ARGB surface
236     lcd /// LCD subpixel quality to ARGB surface with background color (from SDL_ttf 2.20)
237 }
238 
239 static if (sdlTTFSupport >= SDLTTFSupport.v2_20) {
240     /++
241      + D enum that wraps `TTF_WRAPPED_ALIGN_*` enumerations (from SDL_ttf 2.20)
242      +/
243     enum WrappedAlign {
244         /++
245          + Wraps `TTF_WRAPPED_ALIGN_*` enumeration constants
246          +/
247         left = TTF_WRAPPED_ALIGN_LEFT,
248         center = TTF_WRAPPED_ALIGN_CENTER, /// ditto
249         right = TTF_WRAPPED_ALIGN_RIGHT /// ditto
250     }
251 
252     /++
253      + D enum that wraps `TTF_Direction` (from SDL_ttf 2.20)
254      +/
255     enum Direction {
256         /++
257          + Wraps `TTF_DIRECTION_*` enumeration constants
258          +/
259         leftToRight = TTF_DIRECTION_LTR,
260         rightToLeft = TTF_DIRECTION_RTL, /// ditto
261         topToBottom = TTF_DIRECTION_TTB, /// ditto
262         bottomToTop = TTF_DIRECTION_BTT /// ditto
263     }
264 }
265 
266 /++
267  + D class that wraps `TTF_Font` enclosing a font object to render text from
268  +/
269 final class Font {
270     private bool isOwner = true;
271     private void* userRef = null;
272 
273     @system TTF_Font* ttfFont = null; /// Internal `TTF_Font` pointer
274 
275     /++
276      + Constructs a `dsdl2.ttf.Font` from a vanilla `TTF_Font*` from bindbc-sdl
277      +
278      + Params:
279      +   ttfFont = the `TTF_Font` pointer to manage
280      +   isOwner = whether the instance owns the given `TTF_Font*` and should destroy it on its own
281      +   userRef = optional pointer to maintain reference link, avoiding GC cleanup
282      +/
283     this(TTF_Font* ttfFont, bool isOwner = true, void* userRef = null) @system
284     in {
285         assert(ttfFont !is null);
286     }
287     do {
288         this.ttfFont = ttfFont;
289         this.isOwner = isOwner;
290         this.userRef = userRef;
291     }
292 
293     /++
294      + Loads a `dsdl2.ttf.Font` from a font file, which wraps `TTF_OpenFont`
295      +
296      + Params:
297      +   file = path to the font file
298      +   size = point size of the loaded font
299      + Throws: `dsdl2.SDLException` if unable to load the font
300      +/
301     this(string file, uint size) @trusted {
302         this.ttfFont = TTF_OpenFont(file.toStringz(), size.to!int);
303         if (this.ttfFont is null) {
304             throw new SDLException;
305         }
306     }
307 
308     /++
309      + Loads a `dsdl2.ttf.Font` from a font file with a face index, which wraps `TTF_OpenFontIndex`
310      +
311      + Params:
312      +   file = path to the font file
313      +   size = point size of the loaded font
314      +   index = face index of the loaded font
315      + Throws: `dsdl2.SDLException` if unable to load the font
316      +/
317     this(string file, uint size, size_t index) @trusted {
318         this.ttfFont = TTF_OpenFontIndex(file.toStringz(), size.to!int, index.to!c_long);
319         if (this.ttfFont is null) {
320             throw new SDLException;
321         }
322     }
323 
324     /++
325      + Loads a `dsdl2.ttf.Font` from a buffer, which wraps `TTF_OpenFontRW`
326      +
327      + Params:
328      +   data = buffer of the font file
329      +   size = point size of the loaded font
330      + Throws: `dsdl2.SDLException` if unable to load the font
331      +/
332     this(const void[] data, uint size) @trusted {
333         SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
334         if (sdlRWops is null) {
335             throw new SDLException;
336         }
337 
338         this.ttfFont = TTF_OpenFontRW(sdlRWops, 1, size.to!int);
339         if (this.ttfFont is null) {
340             throw new SDLException;
341         }
342     }
343 
344     /++
345      + Loads a `dsdl2.ttf.Font` from a buffer with a face index, which wraps `TTF_OpenFontIndexRW`
346      +
347      + Params:
348      +   data = buffer of the font file
349      +   size = point size of the loaded font
350      +   index = face index of the loaded font
351      + Throws: `dsdl2.SDLException` if unable to load the font
352      +/
353     this(const void[] data, uint size, size_t index) @trusted {
354         SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
355         if (sdlRWops is null) {
356             throw new SDLException;
357         }
358 
359         this.ttfFont = TTF_OpenFontIndexRW(sdlRWops, 1, size.to!int, index.to!c_long);
360         if (this.ttfFont is null) {
361             throw new SDLException;
362         }
363     }
364 
365     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
366         /++
367          + Loads a `dsdl2.ttf.Font` from a font file with DPI, which wraps `TTF_OpenFontDPI` (from SDL_ttf 2.0.18)
368          +
369          + Params:
370          +   file = path to the font file
371          +   size = point size of the loaded font
372          +   hdpi = target horizontal DPI
373          +   vdpi = target vertical DPI
374          + Throws: `dsdl2.SDLException` if unable to load the font
375          +/
376         this(string file, uint size, uint hdpi, uint vdpi) @trusted
377         in {
378             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
379         }
380         do {
381             this.ttfFont = TTF_OpenFontDPI(file.toStringz(), size.to!int, hdpi, vdpi);
382             if (this.ttfFont is null) {
383                 throw new SDLException;
384             }
385         }
386 
387         /++
388          + Loads a `dsdl2.ttf.Font` from a font file with DPI and face index, which wraps `TTF_OpenFontIndexDPI` (from
389          + SDL_ttf 2.0.18)
390          +
391          + Params:
392          +   file = path to the font file
393          +   size = point size of the loaded font
394          +   index = face index of the loaded font
395          +   hdpi = target horizontal DPI
396          +   vdpi = target vertical DPI
397          + Throws: `dsdl2.SDLException` if unable to load the font
398          +/
399         this(string file, uint size, size_t index, uint hdpi, uint vdpi) @trusted
400         in {
401             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
402         }
403         do {
404             this.ttfFont = TTF_OpenFontIndexDPI(file.toStringz(), size.to!int, index.to!c_long, hdpi, vdpi);
405             if (this.ttfFont is null) {
406                 throw new SDLException;
407             }
408         }
409 
410         /++
411          + Loads a `dsdl2.ttf.Font` from a buffer with DPI, which wraps `TTF_OpenFontDPIRW` (from SDL_ttf 2.0.18)
412          +
413          + Params:
414          +   data = buffer of the font file
415          +   size = point size of the loaded font
416          +   hdpi = target horizontal DPI
417          +   vdpi = target vertical DPI
418          + Throws: `dsdl2.SDLException` if unable to load the font
419          +/
420         this(const void[] data, uint size, uint hdpi, uint vdpi) @trusted
421         in {
422             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
423         }
424         do {
425             SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
426             if (sdlRWops is null) {
427                 throw new SDLException;
428             }
429 
430             this.ttfFont = TTF_OpenFontDPIRW(sdlRWops, 1, size.to!int, hdpi, vdpi);
431             if (this.ttfFont is null) {
432                 throw new SDLException;
433             }
434         }
435 
436         /++
437          + Loads a `dsdl2.ttf.Font` from a buffer with DPI and face index, which wraps `TTF_OpenFontIndexDPIRW` (from
438          + SDL_ttf 2.0.18)
439          +
440          + Params:
441          +   data = buffer of the font file
442          +   size = point size of the loaded font
443          +   index = face index of the loaded font
444          +   hdpi = target horizontal DPI
445          +   vdpi = target vertical DPI
446          + Throws: `dsdl2.SDLException` if unable to load the font
447          +/
448         this(const void[] data, uint size, size_t index, uint hdpi, uint vdpi) @trusted
449         in {
450             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
451         }
452         do {
453             SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
454             if (sdlRWops is null) {
455                 throw new SDLException;
456             }
457 
458             this.ttfFont = TTF_OpenFontIndexDPIRW(sdlRWops, 1, size.to!int, index.to!c_long, hdpi, vdpi);
459             if (this.ttfFont is null) {
460                 throw new SDLException;
461             }
462         }
463     }
464 
465     ~this() @trusted {
466         if (this.isOwner) {
467             TTF_CloseFont(this.ttfFont);
468         }
469     }
470 
471     @trusted invariant { // @suppress(dscanner.trust_too_much)
472         // Instance might be in an invalid state due to holding a non-owned externally-freed object when
473         // destructed in an unpredictable order.
474         if (!this.isOwner && GC.inFinalizer) {
475             return;
476         }
477 
478         assert(this.ttfFont !is null);
479     }
480 
481     /++
482      + Equality operator overload
483      +/
484     bool opEquals(const Font rhs) const @trusted {
485         return this.ttfFont is rhs.ttfFont;
486     }
487 
488     /++
489      + Gets the hash of the `dsdl2.ttf.Font`
490      +
491      + Returns: unique hash for the instance being the pointer of the internal `TTF_Font` pointer
492      +/
493     override hash_t toHash() const @trusted {
494         return cast(hash_t) this.ttfFont;
495     }
496 
497     /++
498      + Formats the `dsdl2.ttf.Font` into its construction representation: `"dsdl2.ttf.Font(<ttfFont>)"`
499      +
500      + Returns: the formatted `string`
501      +/
502     override string toString() const @trusted {
503         return "dsdl2.ttf.Font(0x%x)".format(this.ttfFont);
504     }
505 
506     /++
507      + Wraps `TTF_GetFontStyle` to get whether the `dsdl2.ttf.Font` style is bold
508      +
509      + Returns: `true` if the `dsdl2.ttf.Font` is bold, otherwise `false`
510      +/
511     bool bold() const @property @trusted {
512         return (TTF_GetFontStyle(this.ttfFont) & TTF_STYLE_BOLD) == TTF_STYLE_BOLD;
513     }
514 
515     /++
516      + Wraps `TTF_SetFontStyle` to set the `dsdl2.ttf.Font` style to be bold
517      +
518      + Params:
519      +   newBold = `true` to make the `dsdl2.ttf.Font` bold, otherwise `false`
520      +/
521     void bold(bool newBold) @property @trusted {
522         if (newBold) {
523             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) | TTF_STYLE_BOLD);
524         }
525         else {
526             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) & ~TTF_STYLE_BOLD);
527         }
528     }
529 
530     /++
531      + Wraps `TTF_GetFontStyle` to get whether the `dsdl2.ttf.Font` style is italic
532      +
533      + Returns: `true` if the `dsdl2.ttf.Font` is italic, otherwise `false`
534      +/
535     bool italic() const @property @trusted {
536         return (TTF_GetFontStyle(this.ttfFont) & TTF_STYLE_ITALIC) == TTF_STYLE_ITALIC;
537     }
538 
539     /++
540      + Wraps `TTF_SetFontStyle` to set the `dsdl2.ttf.Font` style to be italic
541      +
542      + Params:
543      +   newItalic = `true` to make the `dsdl2.ttf.Font` italic, otherwise `false`
544      +/
545     void italic(bool newItalic) @property @trusted {
546         if (newItalic) {
547             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) | TTF_STYLE_ITALIC);
548         }
549         else {
550             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) & ~TTF_STYLE_ITALIC);
551         }
552     }
553 
554     /++
555      + Wraps `TTF_GetFontStyle` to get whether the `dsdl2.ttf.Font` style is underlined
556      +
557      + Returns: `true` if the `dsdl2.ttf.Font` is underlined, otherwise `false`
558      +/
559     bool underline() const @property @trusted {
560         return (TTF_GetFontStyle(this.ttfFont) & TTF_STYLE_UNDERLINE) == TTF_STYLE_UNDERLINE;
561     }
562 
563     /++
564      + Wraps `TTF_SetFontStyle` to set the `dsdl2.ttf.Font` style to be underlined
565      +
566      + Params:
567      +   newUnderline = `true` to make the `dsdl2.ttf.Font` underlined, otherwise `false`
568      +/
569     void underline(bool newUnderline) @property @trusted {
570         if (newUnderline) {
571             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) | TTF_STYLE_UNDERLINE);
572         }
573         else {
574             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) & ~TTF_STYLE_UNDERLINE);
575         }
576     }
577 
578     /++
579      + Wraps `TTF_GetFontStyle` to get whether the `dsdl2.ttf.Font` style is strikethrough
580      +
581      + Returns: `true` if the `dsdl2.ttf.Font` is strikethrough, otherwise `false`
582      +/
583     bool strikethrough() const @property @trusted {
584         return (TTF_GetFontStyle(this.ttfFont) & TTF_STYLE_STRIKETHROUGH) == TTF_STYLE_STRIKETHROUGH;
585     }
586 
587     /++
588      + Wraps `TTF_SetFontStyle` to set the `dsdl2.ttf.Font` style to be strikethrough
589      +
590      + Params:
591      +   newStrikethrough = `true` to make the `dsdl2.ttf.Font` strikethrough, otherwise `false`
592      +/
593     void strikethrough(bool newStrikethrough) @property @trusted {
594         if (newStrikethrough) {
595             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) | TTF_STYLE_STRIKETHROUGH);
596         }
597         else {
598             TTF_SetFontStyle(this.ttfFont, TTF_GetFontStyle(this.ttfFont) & ~TTF_STYLE_STRIKETHROUGH);
599         }
600     }
601 
602     /++
603      + Wraps `TTF_GetFontOutline` to get the `dsdl2.ttf.Font` outline value
604      +
605      + Returns: `uint` outline value of the `dsdl2.ttf.Font`
606      +/
607     uint outline() const @property @trusted {
608         return TTF_GetFontOutline(this.ttfFont).to!uint;
609     }
610 
611     /++
612      + Wraps `TTF_SetFontOutline` to set the `dsdl2.ttf.Font` outline value
613      +
614      + Params:
615      +   newOutline = new outline value for the `dsdl2.ttf.Font`; `0` to set as default
616      +/
617     void outline(uint newOutline) @property @trusted {
618         TTF_SetFontOutline(this.ttfFont, newOutline.to!int);
619     }
620 
621     /++
622      + Wraps `TTF_GetFontHinting` to get the `dsdl2.ttf.Font` hinting
623      +
624      + Returns: `dsdl2.ttf.Hinting` enumeration for the `dsdl2.ttf.Font` hinting
625      +/
626     Hinting hinting() const @property @trusted {
627         return cast(Hinting) TTF_GetFontHinting(this.ttfFont);
628     }
629 
630     /++
631      + Wraps `TTF_SetFontHinting` to set the `dsdl2.ttf.Font` hinting
632      +
633      + Params:
634      +   newHinting = new `dsdl2.ttf.Hinting` for the `dsdl2.ttf.Font`
635      +/
636     void hinting(Hinting newHinting) @property @trusted {
637         TTF_SetFontHinting(this.ttfFont, newHinting);
638     }
639 
640     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
641         /++
642          + Wraps `TTF_GetFontSDF` (from SDL_ttf 2.0.18) to get whether the `dsdl2.ttf.Font` has signed distance field
643          +
644          + Returns: `true` if the `dsdl2.ttf.Font` has SDF, otherwise `false`
645          +/
646         bool sdf() const @property @trusted
647         in {
648             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
649         }
650         do {
651             return TTF_GetFontSDF(this.ttfFont) == SDL_TRUE;
652         }
653 
654         /++
655          + Wraps `TTF_SetFontSDF` (from SDL_ttf 2.0.18) to set signed distance field
656          +
657          + Params:
658          +   newSDF = `true` to set SDF, otherwise `false`
659          + Throws: `dsdl.SDLException` if unable to set SDF
660          +/
661         void sdf(bool newSDF) @property @trusted
662         in {
663             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
664         }
665         do {
666             if (TTF_SetFontSDF(this.ttfFont, newSDF ? SDL_TRUE : SDL_FALSE) != 0) {
667                 throw new SDLException;
668             }
669         }
670 
671         /++
672          + Wraps `TTF_SetFontSize` (from SDL_ttf 2.0.18) to set the `dsdl2.ttf.Font`'s size
673          +
674          + Params:
675          +   newSize = new size for the `dsdl2.ttf.Font`
676          +/
677         void size(uint newSize) @property @trusted
678         in {
679             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
680         }
681         do {
682             if (TTF_SetFontSize(this.ttfFont, newSize.to!int) != 0) {
683                 throw new SDLException;
684             }
685         }
686 
687         /++
688          + Wraps `TTF_SetFontSizeDPI` (from SDL_ttf 2.0.18) to set the `dsdl2.ttf.Font`'s size in DPI
689          +
690          + Params:
691          +   newSizeDPI = new DPI size tuple of the font size, `hdpi`, and `vdpi`
692          + Throws: `dsdl.SDLException` if unable to set size
693          +/
694         void sizeDPI(Tuple!(uint, uint, uint) newSizeDPI) @property @trusted
695         in {
696             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
697         }
698         do {
699             if (TTF_SetFontSizeDPI(this.ttfFont, newSizeDPI[0].to!int, newSizeDPI[1], newSizeDPI[2]) != 0) {
700                 throw new SDLException;
701             }
702         }
703     }
704 
705     static if (sdlTTFSupport >= SDLTTFSupport.v2_20) {
706         /++
707          + Wraps `TTF_GetFontWrappedAlign` (from SDL_ttf 2.20) to get the `dsdl2.ttf.Font`'s wrap alignment mode
708          +
709          + Returns: `dsdl2.ttf.WrappedAlign` enumeration of the `dsdl2.ttf.Font`
710          +/
711         WrappedAlign wrappedAlign() const @property @trusted
712         in {
713             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
714         }
715         do {
716             return cast(WrappedAlign) TTF_GetFontWrappedAlign(this.ttfFont);
717         }
718 
719         /++
720          + Wraps `TTF_SetFontWrappedAlign` (from SDL_ttf 2.20) to set the `dsdl2.ttf.Font`'s wrap alignment mode
721          +
722          + Params:
723          +   newWrappedAlign = new `dsdl2.ttf.WrappedAlign` of the `dsdl2.ttf.Font`
724          +/
725         void wrappedAlign(WrappedAlign newWrappedAlign) @property @trusted
726         in {
727             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
728         }
729         do {
730             TTF_SetFontWrappedAlign(this.ttfFont, newWrappedAlign);
731         }
732 
733         /++
734          + Wraps `TTF_SetFontDirection` (from SDL_ttf 2.20) to set the `dsdl2.ttf.Font`'s script direction
735          +
736          + Params:
737          +   newDirection = new script `dsdl2.ttf.Direction` for the `dsdl2.ttf.Font`
738          + Throws: `dsdl.SDLException` if unable to set script direction
739          +/
740         void direction(Direction newDirection) @property @trusted
741         in {
742             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
743         }
744         do {
745             if (TTF_SetFontDirection(this.ttfFont, newDirection) != 0) {
746                 throw new SDLException;
747             }
748         }
749 
750         /++
751          + Wraps `TTF_SetFontScriptName` (from SDL_ttf 2.20) to set the `dsdl2.ttf.Font`'s script name
752          +
753          + Params:
754          +   newScriptName = new script name for the `dsdl2.ttf.Font`
755          + Throws: `dsdl.SDLException` if unable to set script name
756          +/
757         void scriptName(string newScriptName) @property @trusted
758         in {
759             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
760         }
761         do {
762             if (TTF_SetFontScriptName(this.ttfFont, newScriptName.toStringz()) != 0) {
763                 throw new SDLException;
764             }
765         }
766     }
767 
768     /++
769      + Wraps `TTF_FontHeight` to get the `dsdl2.ttf.Font`'s height
770      +
771      + Returns: `int` height of the `dsdl2.ttf.Font`
772      +/
773     int height() const @property @trusted {
774         return TTF_FontHeight(this.ttfFont);
775     }
776 
777     /++
778      + Wraps `TTF_FontAscent` to get the `dsdl2.ttf.Font`'s ascent
779      +
780      + Returns: `int` ascent of the `dsdl2.ttf.Font`
781      +/
782     int ascent() const @property @trusted {
783         return TTF_FontAscent(this.ttfFont);
784     }
785 
786     /++
787      + Wraps `TTF_FontDescent` to get the `dsdl2.ttf.Font`'s descent
788      +
789      + Returns: `int` descent of the `dsdl2.ttf.Font`
790      +/
791     int descent() const @property @trusted {
792         return TTF_FontDescent(this.ttfFont);
793     }
794 
795     /++
796      + Wraps `TTF_FontLineSkip` to get the `dsdl2.ttf.Font`'s line skip
797      +
798      + Returns: `int` line skip of the `dsdl2.ttf.Font`
799      +/
800     int lineSkip() const @property @trusted {
801         return TTF_FontLineSkip(this.ttfFont);
802     }
803 
804     /++
805      + Wraps `TTF_GetFontKerning` to get the `dsdl2.ttf.Font`'s kerning
806      +
807      + Returns: `bool` whether the `dsdl2.ttf.Font` has kerning
808      +/
809     bool kerning() const @property @trusted {
810         return TTF_GetFontKerning(this.ttfFont) != 0;
811     }
812 
813     /++
814      + Wraps `TTF_SetFontKerning` to set the `dsdl2.ttf.Font`'s kerning
815      +
816      + Params:
817      +   newKerning = new `bool` value for the `dsdl2.ttf.Font`'s kerning
818      +/
819     void kerning(bool newKerning) @property @trusted {
820         TTF_SetFontKerning(this.ttfFont, newKerning ? 1 : 0);
821     }
822 
823     /++
824      + Wraps `TTF_FontFaces` to get the `dsdl2.ttf.Font`'s number of font faces
825      +
826      + Returns: number of font faces of the `dsdl2.ttf.Font`
827      +/
828     size_t faces() const @property @trusted {
829         return TTF_FontFaces(this.ttfFont);
830     }
831 
832     /++
833      + Wraps `TTF_FontFaceIsFixedWidth` to check whether the `dsdl2.ttf.Font`'s font face is fixed width
834      +
835      + Returns: `true` if the `dsdl2.ttf.Font`'s font face is fixed width, otherwise `false`
836      +/
837     bool fixedWidth() const @property @trusted {
838         return TTF_FontFaceIsFixedWidth(this.ttfFont) != 0;
839     }
840 
841     /++
842      + Wraps `TTF_FontFaceFamilyName` to get the `dsdl2.ttf.Font` family name
843      +
844      + Returns: font family name of the `dsdl2.ttf.Font`
845      +/
846     string familyName() const @property @trusted {
847         return TTF_FontFaceFamilyName(this.ttfFont).to!string;
848     }
849 
850     /++
851      + Wraps `TTF_FontFaceStyleName` to get the `dsdl2.ttf.Font` style name
852      +
853      + Returns: font style name of the `dsdl2.ttf.Font`
854      +/
855     string styleName() const @property @trusted {
856         return TTF_FontFaceStyleName(this.ttfFont).to!string;
857     }
858 
859     /++
860      + Wraps `TTF_GlyphIsProvided` to check whether the `dsdl2.ttf.Font` provides a glyph
861      +
862      + Params:
863      +   glyph = `wchar` glyph to check
864      + Returns: `true` if the `dsdl2.ttf.Font` provides the glyph, otherwise `false`
865      +/
866     bool providesGlyph(wchar glyph) const @trusted {
867         return TTF_GlyphIsProvided(this.ttfFont, cast(ushort) glyph) != 0;
868     }
869 
870     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
871         /++
872          + Wraps `TTF_GlyphIsProvided32` (from SDL_ttf 2.0.18) to check whether the `dsdl2.ttf.Font` provides a glyph
873          +
874          + Params:
875          +   glyph = `dchar` glyph to check
876          + Returns: `true` if the `dsdl2.ttf.Font` provides the glyph, otherwise `false`
877          +/
878         bool providesGlyph(dchar glyph) const @trusted
879         in {
880             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
881         }
882         do {
883             return TTF_GlyphIsProvided32(cast(TTF_Font*) this.ttfFont, cast(uint) glyph) != 0;
884         }
885     }
886 
887     /++
888      + Wraps `TTF_GlyphMetrics` to the metrics of a glyph in the `dsdl2.ttf.Font`
889      +
890      + Params:
891      +   glyph = `wchar` glyph to get the metrics of
892      + Returns: `dsdl2.ttf.GlyphMetrics` of the glyph
893      + Throws: `dsdl.SDLException` if unable to get glyph metrics
894      +/
895     GlyphMetrics glyphMetrics(wchar glyph) const @trusted {
896         GlyphMetrics metrics = void;
897         if (TTF_GlyphMetrics(cast(TTF_Font*) this.ttfFont, cast(ushort) glyph, &metrics.min[0], &metrics.max[0],
898                 &metrics.min[1], &metrics.max[1], &metrics.advance) != 0) {
899             throw new SDLException;
900         }
901 
902         return metrics;
903     }
904 
905     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
906         /++
907          + Wraps `TTF_GlyphMetrics32` (from SDL_ttf 2.0.18) to the metrics of a glyph in the `dsdl2.ttf.Font`
908          +
909          + Params:
910          +   glyph = `dchar` glyph to get the metrics of
911          + Returns: `dsdl2.ttf.GlyphMetrics` of the glyph
912          + Throws: `dsdl.SDLException` if unable to get glyph metrics
913          +/
914         GlyphMetrics glyphMetrics(dchar glyph) const @trusted
915         in {
916             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
917         }
918         do {
919             GlyphMetrics metrics = void;
920             if (TTF_GlyphMetrics32(cast(TTF_Font*) this.ttfFont, cast(uint) glyph, &metrics.min[0], &metrics.max[0],
921                     &metrics.min[1], &metrics.max[1], &metrics.advance) != 0) {
922                 throw new SDLException;
923             }
924 
925             return metrics;
926         }
927     }
928 
929     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_14) {
930         /++
931          + Wraps `TTF_GetFontKerningSize` (from SDL_ttf 2.0.14) to get the kerning between two glyphs in the
932          + `dsdl2.ttf.Font`
933          +
934          + Params:
935          +   prevGlyph = preceeding `wchar` glyph
936          +   glyph = `wchar` glyph
937          + Returns: kerning between `prevGlyph` and `glyph`
938          +/
939         int glyphKerning(wchar prevGlyph, wchar glyph) const @trusted
940         in {
941             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 14));
942         }
943         do {
944             return TTF_GetFontKerningSizeGlyphs(cast(TTF_Font*) this.ttfFont, cast(ushort) prevGlyph,
945                 cast(ushort) glyph);
946         }
947     }
948 
949     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
950         /++
951          + Wraps `TTF_GetFontKerningSize` (from SDL_ttf 2.0.18) to get the kerning between two glyphs in the
952          + `dsdl2.ttf.Font`
953          +
954          + Params:
955          +   prevGlyph = preceeding `dchar` glyph
956          +   glyph = `dchar` glyph
957          + Returns: kerning between `prevGlyph` and `glyph`
958          +/
959         int glyphKerning(dchar prevGlyph, dchar glyph) const @trusted
960         in {
961             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
962         }
963         do {
964             return TTF_GetFontKerningSizeGlyphs32(cast(TTF_Font*) this.ttfFont, cast(uint) prevGlyph,
965                 cast(uint) glyph);
966         }
967     }
968 
969     /++
970      + Wraps `TTF_SizeUTF8` to get the size of a rendered text in the `dsdl2.ttf.Font`
971      +
972      + Params:
973      +   text = rendered `string` text to get the size of
974      + Returns: the size of the rendered text (width and height)
975      + Throws: `dsdl.SDLException` if unable to get text size
976      +/
977     uint[2] textSize(string text) const @trusted {
978         uint[2] wh = void;
979         if (TTF_SizeUTF8(cast(TTF_Font*) this.ttfFont, text.toStringz(), cast(int*)&wh[0], cast(int*)&wh[1]) != 0) {
980             throw new SDLException;
981         }
982 
983         return wh;
984     }
985 
986     /++
987      + Wraps `TTF_SizeUNICODE` to get the size of a rendered text in the `dsdl2.ttf.Font`
988      +
989      + Params:
990      +   text = rendered `wstring` text to get the size of
991      + Returns: the size of the rendered text (width and height)
992      + Throws: `dsdl.SDLException` if unable to get text size
993      +/
994     uint[2] textSize(wstring text) const @trusted {
995         // `std.string.toStringz` doesn't have any `wstring` overloads.
996         wchar[] ctext;
997         ctext.length = text.length + 1;
998         ctext[0 .. text.length] = text;
999         ctext[text.length] = '\0';
1000 
1001         uint[2] wh = void;
1002         if (TTF_SizeUNICODE(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr, cast(int*)&wh[0],
1003                 cast(int*)&wh[1]) != 0) {
1004             throw new SDLException;
1005         }
1006 
1007         return wh;
1008     }
1009 
1010     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1011         private alias TextMeasurement = Tuple!(uint, "extent", uint, "count");
1012 
1013         /++
1014          + Wraps `TTF_MeasureUTF8` (from SDL_ttf 2.0.18) to calculate the maximum number of characters from a text that
1015          + can be rendered given a maximum fitting width
1016          +
1017          + Params:
1018          +   text = `string` of characters to measure
1019          +   measureWidth = maximum fitting width
1020          + Returns: named tuple of `extent` (calculated width) and `count` (number of characters)
1021          + Throws: `dsdl.SDLException` if unable to measure text
1022          +/
1023         TextMeasurement measureText(string text, uint measureWidth) const @trusted
1024         in {
1025             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
1026         }
1027         do {
1028             TextMeasurement measurement = void;
1029             if (TTF_MeasureUTF8(cast(TTF_Font*) this.ttfFont, text.toStringz(), measureWidth.to!int,
1030                     cast(int*)&measurement.extent, cast(int*)&measurement.count) != 0) {
1031                 throw new SDLException;
1032             }
1033 
1034             return measurement;
1035         }
1036 
1037         /++
1038          + Wraps `TTF_MeasureUNICODE` (from SDL_ttf 2.0.18) to calculate the maximum number of characters from a text
1039          + that can be rendered given a maximum fitting width
1040          +
1041          + Params:
1042          +   text = `wstring` of characters to measure
1043          +   measureWidth = maximum fitting width
1044          + Returns: named tuple of `extent` (calculated width) and `count` (number of characters)
1045          + Throws: `dsdl.SDLException` if unable to measure text
1046          +/
1047         TextMeasurement measureText(wstring text, uint measureWidth) const @trusted
1048         in {
1049             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
1050         }
1051         do {
1052             // `std.string.toStringz` doesn't have any `wstring` overloads.
1053             wchar[] ctext;
1054             ctext.length = text.length + 1;
1055             ctext[0 .. text.length] = text;
1056             ctext[text.length] = '\0';
1057 
1058             TextMeasurement measurement = void;
1059             if (TTF_MeasureUNICODE(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr, measureWidth.to!int,
1060                     cast(int*)&measurement[0], cast(int*)&measurement[1]) != 0) {
1061                 throw new SDLException;
1062             }
1063 
1064             return measurement;
1065         }
1066     }
1067 
1068     /++
1069      + Wraps `TTF_RenderGlyph_Solid`, `TTF_RenderGlyph_Shaded`, `TTF_RenderGlyph_Blended`, and additionally
1070      + `TTF_RenderGlyph_LCD` (from SDL_ttf 2.20) to render a glyph in the `dsdl2.ttf.Font`
1071      +
1072      + Params:
1073      +   glyph = `wchar` glyph to render
1074      +   foreground = foreground `dsdl2.Color` of the glyph
1075      +   background = background `dsdl2.Color` of the resulted surface (only for `RenderQuality.shaded` and
1076      +                `RenderQuality.lcd`)
1077      +   quality = `dsdl2.ttf.RenderQuality` of the resulted render
1078      + Returns: `dsdl2.Surface` containing the rendered glyph
1079      + Throws: `dsdl.SDLException` if failed to render glyph
1080      +/
1081     Surface render(wchar glyph, Color foreground, Color background = Color(0, 0, 0, 0),
1082         RenderQuality quality = RenderQuality.shaded) const @trusted
1083     in {
1084         if (quality == RenderQuality.lcd) {
1085             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
1086         }
1087 
1088         if (background != Color(0, 0, 0, 0)) {
1089             assert(quality == RenderQuality.shaded || quality == RenderQuality.lcd, "Only shaded and LCD render quality"
1090                     ~ " can have a background color.");
1091         }
1092     }
1093     do {
1094         SDL_Surface* sdlSurface;
1095         switch (quality) {
1096         case RenderQuality.solid:
1097             sdlSurface = TTF_RenderGlyph_Solid(cast(TTF_Font*) this.ttfFont, cast(ushort) glyph, foreground.sdlColor);
1098             break;
1099 
1100         case RenderQuality.shaded:
1101             sdlSurface = TTF_RenderGlyph_Shaded(cast(TTF_Font*) this.ttfFont, cast(ushort) glyph, foreground.sdlColor,
1102                 background.sdlColor);
1103             break;
1104 
1105         case RenderQuality.blended:
1106             sdlSurface = TTF_RenderGlyph_Blended(cast(TTF_Font*) this.ttfFont, cast(ushort) glyph, foreground.sdlColor);
1107             break;
1108 
1109         case RenderQuality.lcd:
1110             static if (sdlTTFSupport >= SDLTTFSupport.v2_20) {
1111                 sdlSurface = TTF_RenderGlyph_LCD(cast(TTF_Font*) this.ttfFont, cast(ushort) glyph, foreground.sdlColor,
1112                     background.sdlColor);
1113                 break;
1114             }
1115             assert(0);
1116 
1117         default:
1118             assert(0);
1119         }
1120 
1121         if (sdlSurface is null) {
1122             throw new SDLException;
1123         }
1124 
1125         return new Surface(sdlSurface);
1126     }
1127 
1128     static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1129         /++
1130          + Wraps `TTF_RenderGlyph32_Solid`, `TTF_RenderGlyph32_Shaded`, `TTF_RenderGlyph32_Blended` (from SDL_ttf
1131          + 2.0.18), and additionally `TTF_RenderGlyph32_LCD` (from SDL_ttf 2.20) to render a glyph in the
1132          + `dsdl2.ttf.Font`
1133          +
1134          + Params:
1135          +   glyph = `dchar` glyph to render
1136          +   foreground = foreground `dsdl2.Color` of the glyph
1137          +   background = background `dsdl2.Color` of the resulted surface (only for `RenderQuality.shaded` and
1138          +                `RenderQuality.lcd`)
1139          +   quality = `dsdl2.ttf.RenderQuality` of the resulted render
1140          + Returns: `dsdl2.Surface` containing the rendered glyph
1141          + Throws: `dsdl.SDLException` if failed to render glyph
1142          +/
1143         Surface render(dchar glyph, Color foreground, Color background = Color(0, 0, 0, 0),
1144             RenderQuality quality = RenderQuality.shaded) const @trusted
1145         in {
1146             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
1147 
1148             if (quality == RenderQuality.lcd) {
1149                 assert(dsdl2.ttf.getVersion() >= Version(2, 20));
1150             }
1151 
1152             if (background != Color(0, 0, 0, 0)) {
1153                 assert(quality == RenderQuality.shaded || quality == RenderQuality.lcd, "Only shaded and LCD render "
1154                         ~ "quality can have a background color.");
1155             }
1156         }
1157         do {
1158             SDL_Surface* sdlSurface;
1159             switch (quality) {
1160             case RenderQuality.solid:
1161                 sdlSurface = TTF_RenderGlyph32_Solid(cast(TTF_Font*) this.ttfFont, cast(uint) glyph,
1162                     foreground.sdlColor);
1163                 break;
1164 
1165             case RenderQuality.shaded:
1166                 sdlSurface = TTF_RenderGlyph32_Shaded(cast(TTF_Font*) this.ttfFont, cast(uint) glyph,
1167                     foreground.sdlColor, background.sdlColor);
1168                 break;
1169 
1170             case RenderQuality.blended:
1171                 sdlSurface = TTF_RenderGlyph32_Blended(cast(TTF_Font*) this.ttfFont, cast(uint) glyph,
1172                     foreground.sdlColor);
1173                 break;
1174 
1175             case RenderQuality.lcd:
1176                 static if (sdlTTFSupport >= SDLTTFSupport.v2_20) {
1177                     sdlSurface = TTF_RenderGlyph32_LCD(cast(TTF_Font*) this.ttfFont, cast(uint) glyph,
1178                         foreground.sdlColor, background.sdlColor);
1179                     break;
1180                 }
1181                 assert(0);
1182 
1183             default:
1184                 assert(0);
1185             }
1186 
1187             if (sdlSurface is null) {
1188                 throw new SDLException;
1189             }
1190 
1191             return new Surface(sdlSurface);
1192         }
1193     }
1194 
1195     /++
1196      + Wraps `TTF_RenderUTF8_Solid`, `TTF_RenderUTF8_Shaded`, `TTF_RenderUTF8_Blended`, and additionally
1197      + `TTF_RenderUTF8_LCD` (from SDL_ttf 2.20), as well as `TTF_RenderUTF8_Solid_Wrapped`,
1198      + `TTF_RenderUTF8_Shaded_Wrapped`, `TTF_RenderUTF8_Blended_Wrapped` (from SDL_ttf 2.0.18),
1199      + and `TTF_RenderUTF8_LCD_Wrapped` (from SDL_ttf 2.20) to render a text string in the `dsdl2.ttf.Font`
1200      +
1201      + Params:
1202      +   text = `string` text to render
1203      +   foreground = foreground `dsdl2.Color` of the text
1204      +   background = background `dsdl2.Color` of the resulted surface (only for `RenderQuality.shaded` and
1205      +                `RenderQuality.lcd`)
1206      +   quality = `dsdl2.ttf.RenderQuality` of the resulted render
1207      +   wrapLength = maximum width in pixels for wrapping text to the new line; `0` to only wrap on line breaks
1208      +                (wrapping only available from SDL_ttf 2.0.18)
1209      + Returns: `dsdl2.Surface` containing the rendered text
1210      + Throws: `dsdl.SDLException` if failed to render text
1211      +/
1212     Surface render(string text, Color foreground, Color background = Color(0, 0, 0, 0),
1213         RenderQuality quality = RenderQuality.shaded, uint wrapLength = cast(uint)-1) const @trusted
1214     in {
1215         if (quality == RenderQuality.lcd) {
1216             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
1217         }
1218 
1219         if (wrapLength != cast(uint)-1) {
1220             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
1221         }
1222 
1223         if (background != Color(0, 0, 0, 0)) {
1224             assert(quality == RenderQuality.shaded || quality == RenderQuality.lcd, "Only shaded and LCD render quality"
1225                     ~ " can have a background color.");
1226         }
1227     }
1228     do {
1229         immutable char* ctext = text.toStringz();
1230 
1231         SDL_Surface* sdlSurface;
1232         switch (quality) {
1233         case RenderQuality.solid:
1234             if (wrapLength != cast(uint)-1) {
1235                 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1236                     sdlSurface = TTF_RenderUTF8_Solid_Wrapped(cast(TTF_Font*) this.ttfFont, ctext, foreground.sdlColor,
1237                         wrapLength);
1238                 }
1239             }
1240             else {
1241                 sdlSurface = TTF_RenderUTF8_Solid(cast(TTF_Font*) this.ttfFont, ctext, foreground.sdlColor);
1242             }
1243             break;
1244 
1245         case RenderQuality.shaded:
1246             if (wrapLength != cast(uint)-1) {
1247                 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1248                     sdlSurface = TTF_RenderUTF8_Shaded_Wrapped(cast(TTF_Font*) this.ttfFont, ctext,
1249                         foreground.sdlColor, background.sdlColor, wrapLength);
1250                 }
1251             }
1252             else {
1253                 sdlSurface = TTF_RenderUTF8_Shaded(cast(TTF_Font*) this.ttfFont, ctext, foreground.sdlColor,
1254                     background.sdlColor);
1255             }
1256             break;
1257 
1258         case RenderQuality.blended:
1259             if (wrapLength != cast(uint)-1) {
1260                 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1261                     sdlSurface = TTF_RenderUTF8_Blended_Wrapped(cast(TTF_Font*) this.ttfFont, ctext,
1262                         foreground.sdlColor, wrapLength);
1263                 }
1264             }
1265             else {
1266                 sdlSurface = TTF_RenderUTF8_Blended(cast(TTF_Font*) this.ttfFont, ctext, foreground.sdlColor);
1267             }
1268             break;
1269 
1270         case RenderQuality.lcd:
1271             static if (sdlTTFSupport >= SDLTTFSupport.v2_20) {
1272                 if (wrapLength != cast(uint)-1) {
1273                     sdlSurface = TTF_RenderUTF8_LCD_Wrapped(cast(TTF_Font*) this.ttfFont, ctext, foreground.sdlColor,
1274                         background.sdlColor, wrapLength);
1275                 }
1276                 else {
1277                     sdlSurface = TTF_RenderUTF8_LCD(cast(TTF_Font*) this.ttfFont, ctext, foreground.sdlColor,
1278                         background.sdlColor);
1279                 }
1280                 break;
1281             }
1282             assert(0);
1283 
1284         default:
1285             assert(0);
1286         }
1287 
1288         if (sdlSurface is null) {
1289             throw new SDLException;
1290         }
1291 
1292         return new Surface(sdlSurface);
1293     }
1294 
1295     /++
1296      + Wraps `TTF_RenderUNICODE_Solid`, `TTF_RenderUNICODE_Shaded`, `TTF_RenderUNICODE_Blended`, and additionally
1297      + `TTF_RenderUNICODE_LCD` (from SDL_ttf 2.20), as well as `TTF_RenderUNICODE_Solid_Wrapped`,
1298      + `TTF_RenderUNICODE_Shaded_Wrapped`, `TTF_RenderUNICODE_Blended_Wrapped` (from SDL_ttf 2.0.18),
1299      + and `TTF_RenderUNICODE_LCD_Wrapped` (from SDL_ttf 2.20) to render a text string in the `dsdl2.ttf.Font`
1300      +
1301      + Params:
1302      +   text = `wstring` text to render
1303      +   foreground = foreground `dsdl2.Color` of the text
1304      +   background = background `dsdl2.Color` of the resulted surface (only for `RenderQuality.shaded` and
1305      +                `RenderQuality.lcd`)
1306      +   quality = `dsdl2.ttf.RenderQuality` of the resulted render
1307      +   wrapLength = maximum width in pixels for wrapping text to the new line; `0` to only wrap on line breaks
1308      +                (wrapping only available from SDL_ttf 2.0.18)
1309      + Returns: `dsdl2.Surface` containing the rendered text
1310      + Throws: `dsdl.SDLException` if failed to render text
1311      +/
1312     Surface render(wstring text, Color foreground, Color background = Color(0, 0, 0, 0),
1313         RenderQuality quality = RenderQuality.shaded, uint wrapLength = cast(uint)-1) const @trusted
1314     in {
1315         if (quality == RenderQuality.lcd) {
1316             assert(dsdl2.ttf.getVersion() >= Version(2, 20));
1317         }
1318 
1319         if (wrapLength != cast(uint)-1) {
1320             assert(dsdl2.ttf.getVersion() >= Version(2, 0, 18));
1321         }
1322 
1323         if (background != Color(0, 0, 0, 0)) {
1324             assert(quality == RenderQuality.shaded || quality == RenderQuality.lcd, "Only shaded and LCD render quality"
1325                     ~ " can have a background color.");
1326         }
1327     }
1328     do {
1329         // `std.string.toStringz` doesn't have any `wstring` overloads.
1330         wchar[] ctext;
1331         ctext.length = text.length + 1;
1332         ctext[0 .. text.length] = text;
1333         ctext[text.length] = '\0';
1334 
1335         SDL_Surface* sdlSurface;
1336         switch (quality) {
1337         case RenderQuality.solid:
1338             if (wrapLength != cast(uint)-1) {
1339                 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1340                     sdlSurface = TTF_RenderUNICODE_Solid_Wrapped(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr,
1341                         foreground.sdlColor, wrapLength);
1342                 }
1343             }
1344             else {
1345                 sdlSurface = TTF_RenderUNICODE_Solid(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr,
1346                     foreground.sdlColor);
1347             }
1348             break;
1349 
1350         case RenderQuality.shaded:
1351             if (wrapLength != cast(uint)-1) {
1352                 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1353                     sdlSurface = TTF_RenderUNICODE_Shaded_Wrapped(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext
1354                             .ptr,
1355                         foreground.sdlColor, background.sdlColor, wrapLength);
1356                 }
1357             }
1358             else {
1359                 sdlSurface = TTF_RenderUNICODE_Shaded(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr,
1360                     foreground.sdlColor, background.sdlColor);
1361             }
1362             break;
1363 
1364         case RenderQuality.blended:
1365             if (wrapLength != cast(uint)-1) {
1366                 static if (sdlTTFSupport >= SDLTTFSupport.v2_0_18) {
1367                     sdlSurface = TTF_RenderUNICODE_Blended_Wrapped(cast(TTF_Font*) this.ttfFont,
1368                         cast(ushort*) ctext.ptr, foreground.sdlColor, wrapLength);
1369                 }
1370             }
1371             else {
1372                 sdlSurface = TTF_RenderUNICODE_Blended(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr,
1373                     foreground.sdlColor);
1374             }
1375             break;
1376 
1377         case RenderQuality.lcd:
1378             static if (sdlTTFSupport >= SDLTTFSupport.v2_20) {
1379                 if (wrapLength != cast(uint)-1) {
1380                     sdlSurface = TTF_RenderUNICODE_LCD_Wrapped(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr,
1381                         foreground.sdlColor, background.sdlColor, wrapLength);
1382                 }
1383                 else {
1384                     sdlSurface = TTF_RenderUNICODE_LCD(cast(TTF_Font*) this.ttfFont, cast(ushort*) ctext.ptr,
1385                         foreground.sdlColor, background.sdlColor);
1386                 }
1387                 break;
1388             }
1389             assert(0);
1390 
1391         default:
1392             assert(0);
1393         }
1394 
1395         if (sdlSurface is null) {
1396             throw new SDLException;
1397         }
1398 
1399         return new Surface(sdlSurface);
1400     }
1401 }