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.ui.textwrap;
14 
15 import std.uni : isWhite;
16 import std.conv : to;
17 
18 import novelate.fonts;
19 import novelate.external : ExternalText;
20 import novelate.buildstate;
21 
22 /**
23 * 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.
24 * Params:
25 *   text = The text to wrap.
26 *   fontName = The font used for the text.
27 *   fontSize = The size of the font used.
28 *   width = The width to wrap the text at when it exceeds.
29 * Returns:
30 *   A string that is wrapable based on the original given string and conditions.
31 */
32 dstring wrapableText(dstring text, string fontName, size_t fontSize, size_t width)
33 {
34   if (!text || !text.length)
35   {
36     return "";
37   }
38 
39   width -= (fontSize / 2);
40 
41   auto font = retrieveFont(fontName, FontStyle.normal);
42 
43   bool[] splitIndexes = new bool[text.length];
44   bool[] includeSplitters = new bool[text.length];
45 
46   dstring calculateText = "";
47 
48   size_t lastWhiteIndex = 0;
49   bool hasForeignCharacters = false;
50 
51   foreach (ref i; 0 .. text.length)
52   {
53     dchar c = text[i];
54     bool isForeighnCharacter = cast(ushort)c > 128;
55 
56     if (!hasForeignCharacters && isForeighnCharacter)
57     {
58       width -= cast(size_t)(cast(double)fontSize * 1.2);
59 
60       hasForeignCharacters = true;
61     }
62 
63     if (c.isWhite || isForeighnCharacter)
64     {
65       lastWhiteIndex = i;
66     }
67 
68     calculateText ~= c;
69 
70     auto textInstance = new ExternalText;
71     textInstance.setFont(font);
72     textInstance.setString(calculateText);
73     textInstance.setCharacterSize(fontSize);
74 
75     auto textWidth = textInstance.bounds.x;
76 
77     static if (isManualMemory)
78     {
79       textInstance.clean();
80     }
81 
82     if (textWidth >= width)
83     {
84       splitIndexes[lastWhiteIndex] = true;
85       includeSplitters[lastWhiteIndex] = isForeighnCharacter;
86       calculateText = "";
87 
88       i = lastWhiteIndex + 1;
89     }
90   }
91 
92   calculateText = "";
93 
94   foreach (i; 0 .. text.length)
95   {
96     dchar c = text[i];
97 
98     if (splitIndexes[i])
99     {
100       if (includeSplitters[i])
101       {
102         calculateText ~= c ~ to!dstring("\r\n");
103       }
104       else
105       {
106         calculateText ~= "\r\n";
107       }
108     }
109     else
110     {
111       calculateText ~= c;
112     }
113   }
114 
115   return calculateText;
116 }