1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include "BsCorePrerequisites.h"
6#include "Text/BsFontDesc.h"
7#include "Math/BsVector2I.h"
8
9namespace bs
10{
11 /** @addtogroup Implementation
12 * @{
13 */
14
15 /**
16 * This object takes as input a string, a font and optionally some constraints (like word wrap) and outputs a set of
17 * character data you may use for rendering or metrics.
18 */
19 class TextDataBase
20 {
21 protected:
22 /**
23 * Represents a single word as a set of characters, or optionally just a blank space of a certain length.
24 *
25 * @note Due to the way allocation is handled, this class is not allowed to have a destructor.
26 */
27 class TextWord
28 {
29 public:
30 /**
31 * Initializes the word and signals if it just a space (or multiple spaces), or an actual word with letters.
32 */
33 void init(bool spacer);
34
35 /**
36 * Appends a new character to the word.
37 *
38 * @param[in] charIdx Sequential index of the character in the original string.
39 * @param[in] desc Character description from the font.
40 * @return How many pixels did the added character expand the word by.
41 */
42 UINT32 addChar(UINT32 charIdx, const CharDesc& desc);
43
44 /** Adds a space to the word. Word must have previously have been declared as a "spacer". */
45 void addSpace(UINT32 spaceWidth);
46
47 /** Returns the width of the word in pixels. */
48 UINT32 getWidth() const { return mWidth; }
49
50 /** Returns height of the word in pixels. */
51 UINT32 getHeight() const { return mHeight; }
52
53 /**
54 * Calculates new width of the word if we were to add the provided character, without actually adding it.
55 *
56 * @param[in] desc Character description from the font.
57 * @return Width of the word in pixels with the character appended to it.
58 */
59 UINT32 calcWidthWithChar(const CharDesc& desc);
60
61 /**
62 * Returns true if word is a spacer. Spacers contain just a space of a certain length with no actual characters.
63 */
64 bool isSpacer() const { return mSpacer; }
65
66 /** Returns the number of characters in the word. */
67 UINT32 getNumChars() const { return mLastChar == nullptr ? 0 : (mCharsEnd - mCharsStart + 1); }
68
69 /** Returns the index of the starting character in the word. */
70 UINT32 getCharsStart() const { return mCharsStart; }
71
72 /** Returns the index of the last character in the word. */
73 UINT32 getCharsEnd() const { return mCharsEnd; }
74
75 /**
76 * Calculates width of the character by which it would expand the width of the word if it was added to it.
77 *
78 * @param[in] prevDesc Descriptor of the character preceding the one we need the width for. Can be null.
79 * @param[in] desc Character description from the font.
80 * @return How many pixels would the added character expand the word by.
81 */
82 static UINT32 calcCharWidth(const CharDesc* prevDesc, const CharDesc& desc);
83
84 private:
85 UINT32 mCharsStart, mCharsEnd;
86 UINT32 mWidth;
87 UINT32 mHeight;
88
89 const CharDesc* mLastChar;
90
91 bool mSpacer;
92 UINT32 mSpaceWidth;
93 };
94
95 /**
96 * Contains information about a single texture page that contains rendered character data.
97 *
98 * @note Due to the way allocation is handled, this class is not allowed to have a destructor.
99 */
100 struct PageInfo
101 {
102 UINT32 numQuads;
103 HTexture texture;
104 };
105
106 public:
107 /**
108 * Represents a single line as a set of words.
109 *
110 * @note Due to the way allocation is handled, this class is not allowed to have a destructor.
111 */
112 class BS_CORE_EXPORT TextLine
113 {
114 public:
115 /** Returns width of the line in pixels. */
116 UINT32 getWidth() const { return mWidth; }
117
118 /** Returns height of the line in pixels. */
119 UINT32 getHeight() const { return mHeight; }
120
121 /** Returns an offset used to separate two lines. */
122 UINT32 getYOffset() const { return mTextData->getLineHeight(); }
123
124 /**
125 * Calculates new width of the line if we were to add the provided character, without actually adding it.
126 *
127 * @param[in] desc Character description from the font.
128 * @return Width of the line in pixels with the character appended to it.
129 */
130 UINT32 calcWidthWithChar(const CharDesc& desc);
131
132 /**
133 * Fills the vertex/uv/index buffers for the specified page, with all the character data needed for rendering.
134 *
135 * @param[in] page The page.
136 * @param[out] vertices Pre-allocated array where character vertices will be written.
137 * @param[out] uvs Pre-allocated array where character uv coordinates will be written.
138 * @param[out] indexes Pre-allocated array where character indices will be written.
139 * @param[in] offset Offsets the location at which the method writes to the buffers. Counted as number
140 * of quads.
141 * @param[in] size Total number of quads that can fit into the specified buffers.
142 * @return Number of quads that were written.
143 */
144 UINT32 fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const;
145
146 /** Checks are we at a word boundary (meaning the next added character will start a new word). */
147 bool isAtWordBoundary() const;
148
149 /** Returns the total number of characters on this line. */
150 UINT32 getNumChars() const;
151
152 /**
153 * Query if this line was created explicitly due to a newline character. As opposed to a line that was created
154 * because a word couldn't fit on the previous line.
155 */
156 bool hasNewlineChar() const { return mHasNewline; }
157 private:
158 friend class TextDataBase;
159
160 /**
161 * Appends a new character to the line.
162 *
163 * @param[in] charIdx Sequential index of the character in the original string.
164 * @param[in] desc Character description from the font.
165 */
166 void add(UINT32 charIdx, const CharDesc& charDesc);
167
168 /** Appends a space to the line. */
169 void addSpace(UINT32 spaceWidth);
170
171 /**
172 * Adds a new word to the line.
173 *
174 * @param[in] wordIdx Sequential index of the word in the original string. Spaces are counted as words as
175 * well.
176 * @param[in] word Description of the word.
177 */
178 void addWord(UINT32 wordIdx, const TextWord& word);
179
180 /** Initializes the line. Must be called after construction. */
181 void init(TextDataBase* textData);
182
183 /**
184 * Finalizes the line. Do not add new characters/words after a line has been finalized.
185 *
186 * @param[in] hasNewlineChar Set to true if line was create due to an explicit newline char. As opposed to a
187 * line that was created because a word couldn't fit on the previous line.
188 */
189 void finalize(bool hasNewlineChar);
190
191 /** Returns true if the line contains no words. */
192 bool isEmpty() const { return mIsEmpty; }
193
194 /** Removes last word from the line and returns its sequential index. */
195 UINT32 removeLastWord();
196
197 /** Calculates the line width and height in pixels. */
198 void calculateBounds();
199
200 private:
201 TextDataBase* mTextData;
202 UINT32 mWordsStart, mWordsEnd;
203
204 UINT32 mWidth;
205 UINT32 mHeight;
206
207 bool mIsEmpty;
208 bool mHasNewline;
209 };
210
211 public:
212 /**
213 * Initializes a new text data using the specified string and font. Text will attempt to fit into the provided area.
214 * If enabled it will wrap words to new line when they don't fit. Individual words will be broken into multiple
215 * pieces if they don't fit on a single line when word break is enabled, otherwise they will be clipped. If the
216 * specified area is zero size then the text will not be clipped or word wrapped in any way.
217 *
218 * After this object is constructed you may call various getter methods to get needed information.
219 */
220 BS_CORE_EXPORT TextDataBase(const U32String& text, const HFont& font, UINT32 fontSize,
221 UINT32 width = 0, UINT32 height = 0, bool wordWrap = false, bool wordBreak = true);
222 BS_CORE_EXPORT virtual ~TextDataBase() = default;
223
224 /** Returns the number of lines that were generated. */
225 BS_CORE_EXPORT UINT32 getNumLines() const { return mNumLines; }
226
227 /** Returns the number of font pages references by the used characters. */
228 BS_CORE_EXPORT UINT32 getNumPages() const { return mNumPageInfos; }
229
230 /** Returns the height of a line in pixels. */
231 BS_CORE_EXPORT UINT32 getLineHeight() const;
232
233 /** Gets information describing a single line at the specified index. */
234 BS_CORE_EXPORT const TextLine& getLine(UINT32 idx) const { return mLines[idx]; }
235
236 /** Returns font texture for the provided page index. */
237 BS_CORE_EXPORT const HTexture& getTextureForPage(UINT32 page) const;
238
239 /** Returns the number of quads used by all the characters in the provided page. */
240 BS_CORE_EXPORT UINT32 getNumQuadsForPage(UINT32 page) const { return mPageInfos[page].numQuads; }
241
242 /** Returns the width of the actual text in pixels. */
243 BS_CORE_EXPORT UINT32 getWidth() const;
244
245 /** Returns the height of the actual text in pixels. */
246 BS_CORE_EXPORT UINT32 getHeight() const;
247
248 protected:
249 /**
250 * Copies internally stored data in temporary buffers to a persistent buffer.
251 *
252 * @param[in] text Text originally used for creating the internal temporary buffer data.
253 * @param[in] buffer Memory location to copy the data to. If null then no data will be copied and the
254 * parameter @p size will contain the required buffer size.
255 * @param[in] size Size of the provided memory buffer, or if the buffer is null, this will contain the
256 * required buffer size after method exists.
257 * @param[in] freeTemporary If true the internal temporary data will be freed after copying.
258 *
259 * @note Must be called after text data has been constructed and is in the temporary buffers.
260 */
261 BS_CORE_EXPORT void generatePersistentData(const U32String& text, UINT8* buffer, UINT32& size,
262 bool freeTemporary = true);
263 private:
264 friend class TextLine;
265
266 /** Returns Y offset that determines the line on which the characters are placed. In pixels. */
267 INT32 getBaselineOffset() const;
268
269 /** Returns the width of a single space in pixels. */
270 UINT32 getSpaceWidth() const;
271
272 /** Gets a description of a single character referenced by its sequential index based on the original string. */
273 const CharDesc& getChar(UINT32 idx) const { return *mChars[idx]; }
274
275 /** Gets a description of a single word referenced by its sequential index based on the original string. */
276 const TextWord& getWord(UINT32 idx) const { return mWords[idx]; }
277
278 protected:
279 const CharDesc** mChars;
280 UINT32 mNumChars;
281
282 TextWord* mWords;
283 UINT32 mNumWords;
284
285 TextLine* mLines;
286 UINT32 mNumLines;
287
288 PageInfo* mPageInfos;
289 UINT32 mNumPageInfos;
290
291 HFont mFont;
292 SPtr<const FontBitmap> mFontData;
293
294 // Static buffers used to reduce runtime memory allocation
295 protected:
296 /** Stores per-thread memory buffers used to reduce memory allocation. */
297 // Note: I could replace this with the global frame allocator to avoid the extra logic
298 struct BufferData
299 {
300 BufferData();
301 ~BufferData();
302
303 /**
304 * Allocates a new word and adds it to the buffer. Returns index of the word in the word buffer.
305 *
306 * @param[in] spacer Specify true if the word is only to contain spaces. (Spaces are considered a special
307 * type of word).
308 */
309 UINT32 allocWord(bool spacer);
310
311 /** Allocates a new line and adds it to the buffer. Returns index of the line in the line buffer. */
312 UINT32 allocLine(TextDataBase* textData);
313
314 /**
315 * Increments the count of characters for the referenced page, and optionally creates page info if it doesn't
316 * already exist.
317 */
318 void addCharToPage(UINT32 page, const FontBitmap& fontData);
319
320 /** Resets all allocation counters, but doesn't actually release memory. */
321 void deallocAll();
322
323 TextWord* WordBuffer;
324 UINT32 WordBufferSize;
325 UINT32 NextFreeWord;
326
327 TextLine* LineBuffer;
328 UINT32 LineBufferSize;
329 UINT32 NextFreeLine;
330
331 PageInfo* PageBuffer;
332 UINT32 PageBufferSize;
333 UINT32 NextFreePageInfo;
334 };
335
336 static BS_THREADLOCAL BufferData* MemBuffer;
337
338 /** Allocates an initial set of buffers that will be reused while parsing text data. */
339 static void initAlloc();
340 };
341
342 /** @} */
343
344 /** @addtogroup Text
345 * @{
346 */
347
348 /** @copydoc TextDataBase */
349 template<class Alloc = GenAlloc>
350 class TextData : public TextDataBase
351 {
352 public:
353 /** @copydoc TextDataBase::TextDataBase */
354 TextData(const U32String& text, const HFont& font, UINT32 fontSize,
355 UINT32 width = 0, UINT32 height = 0, bool wordWrap = false, bool wordBreak = true)
356 :TextDataBase(text, font, fontSize, width, height, wordWrap, wordBreak), mData(nullptr)
357 {
358 UINT32 totalBufferSize = 0;
359 generatePersistentData(text, nullptr, totalBufferSize);
360
361 mData = (UINT8*)bs_alloc<Alloc>(totalBufferSize);
362 generatePersistentData(text, (UINT8*)mData, totalBufferSize);
363 }
364
365 ~TextData()
366 {
367 if (mData != nullptr)
368 bs_free<Alloc>(mData);
369 }
370
371 private:
372 UINT8* mData;
373 };
374
375 /** @} */
376}