BsTextData.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. //__________________________ Banshee Project - A modern game development toolkit _________________________________//
  2. //_____________________________________ www.banshee-project.com __________________________________________________//
  3. //________________________ Copyright (c) 2014 Marko Pintera. All rights reserved. ________________________________//
  4. #include "BsTextData.h"
  5. #include "BsFont.h"
  6. #include "BsVector2.h"
  7. #include "BsDebug.h"
  8. namespace BansheeEngine
  9. {
  10. const int SPACE_CHAR = 32;
  11. void TextData::TextWord::init(bool spacer)
  12. {
  13. mWidth = mHeight = 0;
  14. mSpacer = spacer;
  15. mSpaceWidth = 0;
  16. mCharsStart = 0;
  17. mCharsEnd = 0;
  18. mLastChar = nullptr;
  19. }
  20. // Assumes charIdx is an index right after last char in the list (if any). All chars need to be sequential.
  21. UINT32 TextData::TextWord::addChar(UINT32 charIdx, const CHAR_DESC& desc)
  22. {
  23. UINT32 charWidth = desc.xAdvance;
  24. if(mLastChar != nullptr)
  25. {
  26. UINT32 kerning = 0;
  27. for(size_t j = 0; j < mLastChar->kerningPairs.size(); j++)
  28. {
  29. if(mLastChar->kerningPairs[j].otherCharId == desc.charId)
  30. {
  31. kerning = mLastChar->kerningPairs[j].amount;
  32. break;
  33. }
  34. }
  35. charWidth += kerning;
  36. }
  37. mWidth += charWidth;
  38. mHeight = std::max(mHeight, desc.height);
  39. if(mLastChar == nullptr) // First char
  40. mCharsStart = mCharsEnd = charIdx;
  41. else
  42. mCharsEnd = charIdx;
  43. mLastChar = &desc;
  44. return charWidth;
  45. }
  46. void TextData::TextWord::addSpace(UINT32 spaceWidth)
  47. {
  48. mSpaceWidth += spaceWidth;
  49. mWidth = mSpaceWidth;
  50. mHeight = 0;
  51. }
  52. void TextData::TextLine::init(TextData* textData)
  53. {
  54. mWidth = 0;
  55. mHeight = 0;
  56. mIsEmpty = true;
  57. mTextData = textData;
  58. mWordsStart = mWordsEnd = 0;
  59. }
  60. void TextData::TextLine::finalize(bool hasNewlineChar)
  61. {
  62. mHasNewline = hasNewlineChar;
  63. }
  64. void TextData::TextLine::add(UINT32 charIdx, const CHAR_DESC& charDesc)
  65. {
  66. UINT32 charWidth = 0;
  67. if(mIsEmpty)
  68. {
  69. mWordsStart = mWordsEnd = allocWord(false);
  70. mIsEmpty = false;
  71. }
  72. else
  73. {
  74. if(TextData::WordBuffer[mWordsEnd].isSpacer())
  75. mWordsEnd = allocWord(false);
  76. }
  77. TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
  78. charWidth = lastWord.addChar(charIdx, charDesc);
  79. mWidth += charWidth;
  80. mHeight = std::max(mHeight, lastWord.getHeight());
  81. }
  82. void TextData::TextLine::addSpace()
  83. {
  84. if(mIsEmpty)
  85. {
  86. mWordsStart = mWordsEnd = allocWord(true);
  87. mIsEmpty = false;
  88. }
  89. else
  90. mWordsEnd = allocWord(true); // Each space is counted as its own word, to make certain operations easier
  91. TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
  92. lastWord.addSpace(mTextData->getSpaceWidth());
  93. mWidth += mTextData->getSpaceWidth();
  94. }
  95. // Assumes wordIdx is an index right after last word in the list (if any). All words need to be sequential.
  96. void TextData::TextLine::addWord(UINT32 wordIdx, const TextWord& word)
  97. {
  98. if(mIsEmpty)
  99. {
  100. mWordsStart = mWordsEnd = wordIdx;
  101. mIsEmpty = false;
  102. }
  103. else
  104. mWordsEnd = wordIdx;
  105. mWidth += word.getWidth();
  106. mHeight = std::max(mHeight, word.getHeight());
  107. }
  108. UINT32 TextData::TextLine::removeLastWord()
  109. {
  110. if(mIsEmpty)
  111. {
  112. assert(false);
  113. return 0;
  114. }
  115. UINT32 lastWord = mWordsEnd--;
  116. if(mWordsStart == lastWord)
  117. {
  118. mIsEmpty = true;
  119. mWordsStart = mWordsEnd = 0;
  120. }
  121. calculateBounds();
  122. return lastWord;
  123. }
  124. UINT32 TextData::TextLine::fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const
  125. {
  126. UINT32 numQuads = 0;
  127. if(mIsEmpty)
  128. return numQuads;
  129. UINT32 penX = 0;
  130. for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
  131. {
  132. const TextWord& word = mTextData->getWord(i);
  133. if(word.isSpacer())
  134. {
  135. // We store invisible space quads in the first page. Even though they aren't needed
  136. // for rendering and we could just leave an empty space, they are needed for intersection tests
  137. // for things like determining caret placement and selection areas
  138. if(page == 0)
  139. {
  140. INT32 curX = penX;
  141. INT32 curY = 0;
  142. UINT32 curVert = offset * 4;
  143. UINT32 curIndex = offset * 6;
  144. vertices[curVert + 0] = Vector2((float)curX, (float)curY);
  145. vertices[curVert + 1] = Vector2((float)(curX + mTextData->getSpaceWidth()), (float)curY);
  146. vertices[curVert + 2] = Vector2((float)curX, (float)curY + (float)mTextData->getLineHeight());
  147. vertices[curVert + 3] = Vector2((float)(curX + mTextData->getSpaceWidth()), (float)curY + (float)mTextData->getLineHeight());
  148. if(uvs != nullptr)
  149. {
  150. uvs[curVert + 0] = Vector2(0.0f, 0.0f);
  151. uvs[curVert + 1] = Vector2(0.0f, 0.0f);
  152. uvs[curVert + 2] = Vector2(0.0f, 0.0f);
  153. uvs[curVert + 3] = Vector2(0.0f, 0.0f);
  154. }
  155. // Triangles are back-facing which makes them invisible
  156. if(indexes != nullptr)
  157. {
  158. indexes[curIndex + 0] = curVert + 0;
  159. indexes[curIndex + 1] = curVert + 2;
  160. indexes[curIndex + 2] = curVert + 1;
  161. indexes[curIndex + 3] = curVert + 1;
  162. indexes[curIndex + 4] = curVert + 2;
  163. indexes[curIndex + 5] = curVert + 3;
  164. }
  165. offset++;
  166. numQuads++;
  167. if(offset > size)
  168. BS_EXCEPT(InternalErrorException, "Out of buffer bounds. Buffer size: " + toString(size));
  169. }
  170. penX += mTextData->getSpaceWidth();
  171. }
  172. else
  173. {
  174. UINT32 kerning = 0;
  175. for(UINT32 j = word.getCharsStart(); j <= word.getCharsEnd(); j++)
  176. {
  177. const CHAR_DESC& curChar = mTextData->getChar(j);
  178. INT32 curX = penX + curChar.xOffset;
  179. INT32 curY = ((INT32) mTextData->getBaselineOffset() - curChar.yOffset);
  180. penX += curChar.xAdvance + kerning;
  181. kerning = 0;
  182. if((j + 1) <= word.getCharsEnd())
  183. {
  184. const CHAR_DESC& nextChar = mTextData->getChar(j + 1);
  185. for(size_t j = 0; j < curChar.kerningPairs.size(); j++)
  186. {
  187. if(curChar.kerningPairs[j].otherCharId == nextChar.charId)
  188. {
  189. kerning = curChar.kerningPairs[j].amount;
  190. break;
  191. }
  192. }
  193. }
  194. if(curChar.page != page)
  195. continue;
  196. UINT32 curVert = offset * 4;
  197. UINT32 curIndex = offset * 6;
  198. vertices[curVert + 0] = Vector2((float)curX, (float)curY);
  199. vertices[curVert + 1] = Vector2((float)(curX + curChar.width), (float)curY);
  200. vertices[curVert + 2] = Vector2((float)curX, (float)curY + (float)curChar.height);
  201. vertices[curVert + 3] = Vector2((float)(curX + curChar.width), (float)curY + (float)curChar.height);
  202. if(uvs != nullptr)
  203. {
  204. uvs[curVert + 0] = Vector2(curChar.uvX, curChar.uvY);
  205. uvs[curVert + 1] = Vector2(curChar.uvX + curChar.uvWidth, curChar.uvY);
  206. uvs[curVert + 2] = Vector2(curChar.uvX, curChar.uvY + curChar.uvHeight);
  207. uvs[curVert + 3] = Vector2(curChar.uvX + curChar.uvWidth, curChar.uvY + curChar.uvHeight);
  208. }
  209. if(indexes != nullptr)
  210. {
  211. indexes[curIndex + 0] = curVert + 0;
  212. indexes[curIndex + 1] = curVert + 1;
  213. indexes[curIndex + 2] = curVert + 2;
  214. indexes[curIndex + 3] = curVert + 1;
  215. indexes[curIndex + 4] = curVert + 3;
  216. indexes[curIndex + 5] = curVert + 2;
  217. }
  218. offset++;
  219. numQuads++;
  220. if(offset > size)
  221. BS_EXCEPT(InternalErrorException, "Out of buffer bounds. Buffer size: " + toString(size));
  222. }
  223. }
  224. }
  225. return numQuads;
  226. }
  227. UINT32 TextData::TextLine::getNumChars() const
  228. {
  229. if(mIsEmpty)
  230. return 0;
  231. UINT32 numChars = 0;
  232. for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
  233. {
  234. TextWord& word = TextData::WordBuffer[i];
  235. if(word.isSpacer())
  236. numChars++;
  237. else
  238. numChars += (UINT32)word.getNumChars();
  239. }
  240. return numChars;
  241. }
  242. void TextData::TextLine::calculateBounds()
  243. {
  244. mWidth = 0;
  245. mHeight = 0;
  246. if(mIsEmpty)
  247. return;
  248. for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
  249. {
  250. TextWord& word = TextData::WordBuffer[i];
  251. mWidth += word.getWidth();
  252. mHeight = std::max(mHeight, word.getHeight());
  253. }
  254. }
  255. TextData::TextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width, UINT32 height, bool wordWrap)
  256. :mFont(font), mChars(nullptr), mFontData(nullptr),
  257. mNumChars(0), mWords(nullptr), mNumWords(0), mLines(nullptr), mNumLines(0), mPageInfos(nullptr), mNumPageInfos(0), mData(nullptr)
  258. {
  259. // In order to reduce number of memory allocations algorithm first calculates data into temporary buffers and then copies the results
  260. initAlloc();
  261. if(font != nullptr)
  262. {
  263. UINT32 nearestSize = font->getClosestAvailableSize(fontSize);
  264. mFontData = font->getFontDataForSize(nearestSize);
  265. }
  266. if(mFontData == nullptr || mFontData->texturePages.size() == 0)
  267. return;
  268. if(mFontData->size != fontSize)
  269. {
  270. LOGWRN("Unable to find font with specified size (" + toString(fontSize) + "). Using nearest available size: " + toString(mFontData->size));
  271. }
  272. bool widthIsLimited = width > 0;
  273. mFont = font;
  274. UINT32 curLineIdx = allocLine(this);
  275. UINT32 curHeight = mFontData->fontDesc.lineHeight;
  276. UINT32 charIdx = 0;
  277. while(true)
  278. {
  279. if(charIdx >= text.size())
  280. break;
  281. UINT32 charId = text[charIdx];
  282. const CHAR_DESC& charDesc = mFontData->getCharDesc(charId);
  283. TextLine* curLine = &LineBuffer[curLineIdx];
  284. if(text[charIdx] == '\n')
  285. {
  286. curLine->finalize(true);
  287. curLineIdx = allocLine(this);
  288. curLine = &LineBuffer[curLineIdx];
  289. curHeight += mFontData->fontDesc.lineHeight;
  290. charIdx++;
  291. continue;
  292. }
  293. if(charId != SPACE_CHAR)
  294. {
  295. curLine->add(charIdx, charDesc);
  296. addCharToPage(charDesc.page, *mFontData);
  297. }
  298. else
  299. {
  300. curLine->addSpace();
  301. addCharToPage(0, *mFontData);
  302. }
  303. if(widthIsLimited && curLine->getWidth() > width)
  304. {
  305. if(wordWrap)
  306. {
  307. assert(!curLine->isEmpty());
  308. UINT32 lastWordIdx = curLine->removeLastWord();
  309. TextWord& lastWord = WordBuffer[lastWordIdx];
  310. if(lastWord.getWidth() <= width) // If the word fits, attempt to add it to a new line
  311. {
  312. curLine->finalize(false);
  313. curLineIdx = allocLine(this);
  314. curLine = &LineBuffer[curLineIdx];
  315. curHeight += mFontData->fontDesc.lineHeight;
  316. }
  317. curLine->addWord(lastWordIdx, lastWord);
  318. }
  319. }
  320. charIdx++;
  321. }
  322. LineBuffer[curLineIdx].finalize(true);
  323. // Now that we have all the data we need, allocate the permanent buffers and copy the data
  324. mNumChars = (UINT32)text.size();
  325. mNumWords = NextFreeWord;
  326. mNumLines = NextFreeLine;
  327. mNumPageInfos = NextFreePageInfo;
  328. UINT32 charArraySize = mNumChars * sizeof(const CHAR_DESC*);
  329. UINT32 wordArraySize = mNumWords * sizeof(TextWord);
  330. UINT32 lineArraySize = mNumLines * sizeof(TextLine);
  331. UINT32 pageInfoArraySize = mNumPageInfos * sizeof(PageInfo);
  332. UINT32 totalBufferSize = charArraySize + wordArraySize + lineArraySize + pageInfoArraySize;
  333. mData = bs_alloc(totalBufferSize);
  334. UINT8* dataPtr = (UINT8*)mData;
  335. mChars = (const CHAR_DESC**)dataPtr;
  336. for(UINT32 i = 0; i < mNumChars; i++)
  337. {
  338. UINT32 charId = text[i];
  339. const CHAR_DESC& charDesc = mFontData->getCharDesc(charId);
  340. mChars[i] = &charDesc;
  341. }
  342. dataPtr += charArraySize;
  343. mWords = (TextWord*)dataPtr;
  344. memcpy(mWords, &WordBuffer[0], wordArraySize);
  345. dataPtr += wordArraySize;
  346. mLines = (TextLine*)dataPtr;
  347. memcpy(mLines, &LineBuffer[0], lineArraySize);
  348. dataPtr += lineArraySize;
  349. mPageInfos = (PageInfo*)dataPtr;
  350. memcpy(mPageInfos, &PageBuffer[0], pageInfoArraySize);
  351. TextData::deallocAll();
  352. }
  353. TextData::~TextData()
  354. {
  355. if(mData != nullptr)
  356. bs_free(mData);
  357. }
  358. const HTexture& TextData::getTextureForPage(UINT32 page) const
  359. {
  360. return mFontData->texturePages[page];
  361. }
  362. INT32 TextData::getBaselineOffset() const
  363. {
  364. return mFontData->fontDesc.baselineOffset;
  365. }
  366. UINT32 TextData::getLineHeight() const
  367. {
  368. return mFontData->fontDesc.lineHeight;
  369. }
  370. UINT32 TextData::getSpaceWidth() const
  371. {
  372. return mFontData->fontDesc.spaceWidth;
  373. }
  374. bool TextData::BuffersInitialized = false;
  375. TextData::TextWord* TextData::WordBuffer = nullptr;
  376. UINT32 TextData::NextFreeWord = 0;
  377. UINT32 TextData::WordBufferSize = 0;
  378. TextData::TextLine* TextData::LineBuffer = nullptr;
  379. UINT32 TextData::NextFreeLine = 0;
  380. UINT32 TextData::LineBufferSize = 0;
  381. TextData::PageInfo* TextData::PageBuffer = nullptr;
  382. UINT32 TextData::NextFreePageInfo = 0;
  383. UINT32 TextData::PageBufferSize = 0;
  384. void TextData::initAlloc()
  385. {
  386. if(!BuffersInitialized)
  387. {
  388. WordBufferSize = 2000;
  389. LineBufferSize = 500;
  390. PageBufferSize = 20;
  391. WordBuffer = bs_newN<TextWord>(WordBufferSize);
  392. LineBuffer = bs_newN<TextLine>(LineBufferSize);
  393. PageBuffer = bs_newN<PageInfo>(PageBufferSize);
  394. BuffersInitialized = true;
  395. }
  396. }
  397. UINT32 TextData::allocWord(bool spacer)
  398. {
  399. if(NextFreeWord >= WordBufferSize)
  400. {
  401. UINT32 newBufferSize = WordBufferSize * 2;
  402. TextWord* newBuffer = bs_newN<TextWord>(newBufferSize);
  403. memcpy(WordBuffer, newBuffer, WordBufferSize);
  404. bs_deleteN(WordBuffer, WordBufferSize);
  405. WordBuffer = newBuffer;
  406. WordBufferSize = newBufferSize;
  407. }
  408. WordBuffer[NextFreeWord].init(spacer);
  409. return NextFreeWord++;
  410. }
  411. UINT32 TextData::allocLine(TextData* textData)
  412. {
  413. if(NextFreeLine >= LineBufferSize)
  414. {
  415. UINT32 newBufferSize = LineBufferSize * 2;
  416. TextLine* newBuffer = bs_newN<TextLine>(newBufferSize);
  417. memcpy(LineBuffer, newBuffer, LineBufferSize);
  418. bs_deleteN(LineBuffer, LineBufferSize);
  419. LineBuffer = newBuffer;
  420. LineBufferSize = newBufferSize;
  421. }
  422. LineBuffer[NextFreeLine].init(textData);
  423. return NextFreeLine++;
  424. }
  425. void TextData::deallocAll()
  426. {
  427. NextFreeWord = 0;
  428. NextFreeLine = 0;
  429. NextFreePageInfo = 0;
  430. }
  431. void TextData::addCharToPage(UINT32 page, const FontData& fontData)
  432. {
  433. if(NextFreePageInfo >= PageBufferSize)
  434. {
  435. UINT32 newBufferSize = PageBufferSize * 2;
  436. PageInfo* newBuffer = bs_newN<PageInfo>(newBufferSize);
  437. memcpy(PageBuffer, newBuffer, PageBufferSize);
  438. bs_deleteN(PageBuffer, PageBufferSize);
  439. PageBuffer = newBuffer;
  440. PageBufferSize = newBufferSize;
  441. }
  442. while(page >= NextFreePageInfo)
  443. {
  444. PageBuffer[NextFreePageInfo].numQuads = 0;
  445. NextFreePageInfo++;
  446. }
  447. PageBuffer[page].numQuads++;
  448. }
  449. UINT32 TextData::getWidth() const
  450. {
  451. UINT32 width = 0;
  452. for(UINT32 i = 0; i < mNumLines; i++)
  453. width = std::max(width, mLines[i].getWidth());
  454. return width;
  455. }
  456. UINT32 TextData::getHeight() const
  457. {
  458. UINT32 height = 0;
  459. for(UINT32 i = 0; i < mNumLines; i++)
  460. height += mLines[i].getHeight();
  461. return height;
  462. }
  463. }