BsTextData.cpp 16 KB

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