1 /**
2 * Copyright © Novelate 2020
3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 * Website: https://novelate.com/
6 * ------
7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language.
8 * It can be used freely for both personal and commercial projects.
9 * ------
10 * Module Description:
11 * This module handles text wrapping which is an essential feature when working with text that has to fit into a specific width. It's currently not a very performant algorithm but it gets the job done.
12 */
13 module novelate.textwrap;
14 
15 import std.uni : isWhite;
16 import std.conv : to;
17 
18 import novelate.fonts;
19 import novelate.external : ExternalText;
20 
21 /**
22 * Creates text that wraps when it exceeds a given width. It does so by adding a new line at the last space given before the text exceeds the width and does so for all of the text.
23 * Params:
24 *   text = The text to wrap.
25 *   fontName = The font used for the text.
26 *   fontSize = The size of the font used.
27 *   width = The width to wrap the text at when it exceeds.
28 * Returns:
29 *   A string that is wrapable based on the original given string and conditions.
30 */
31 dstring wrapableText(dstring text, string fontName, size_t fontSize, size_t width)
32 {
33   if (!text || !text.length)
34   {
35     return "";
36   }
37 
38   width -= (fontSize / 2);
39 
40   auto font = retrieveFont(fontName, FontStyle.normal);
41 
42   bool[] splitIndexes = new bool[text.length];
43   bool[] includeSplitters = new bool[text.length];
44 
45   dstring calculateText = "";
46 
47   size_t lastWhiteIndex = 0;
48   bool hasForeignCharacters = false;
49 
50   foreach (ref i; 0 .. text.length)
51   {
52     dchar c = text[i];
53     bool isForeighnCharacter = cast(ushort)c > 128;
54 
55     if (!hasForeignCharacters && isForeighnCharacter)
56     {
57       width -= cast(size_t)(cast(double)fontSize * 1.2);
58 
59       hasForeignCharacters = true;
60     }
61 
62     if (c.isWhite || isForeighnCharacter)
63     {
64       lastWhiteIndex = i;
65     }
66 
67     calculateText ~= c;
68 
69     auto textInstance = new ExternalText;
70     textInstance.setFont(font);
71     textInstance.setString(calculateText);
72     textInstance.setCharacterSize(fontSize);
73 
74     auto textWidth = textInstance.bounds.x;
75 
76     if (textWidth >= width)
77     {
78       splitIndexes[lastWhiteIndex] = true;
79       includeSplitters[lastWhiteIndex] = isForeighnCharacter;
80       calculateText = "";
81 
82       i = lastWhiteIndex + 1;
83     }
84   }
85 
86   calculateText = "";
87 
88   foreach (i; 0 .. text.length)
89   {
90     dchar c = text[i];
91 
92     if (splitIndexes[i])
93     {
94       if (includeSplitters[i])
95       {
96         calculateText ~= c ~ to!dstring("\r\n");
97       }
98       else
99       {
100         calculateText ~= "\r\n";
101       }
102     }
103     else
104     {
105       calculateText ~= c;
106     }
107   }
108 
109   return calculateText;
110 }