CmTextData.cpp 14 KB

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