CmTextUtility.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. #include "CmTextUtility.h"
  2. #include "CmFont.h"
  3. #include "CmVector2.h"
  4. #include "CmDebug.h"
  5. namespace CamelotFramework
  6. {
  7. const int SPACE_CHAR = 32;
  8. TextUtility::TextWord::TextWord(bool spacer)
  9. :mWidth(0), mHeight(0), mSpacer(spacer), mSpaceWidth(0)
  10. { }
  11. void TextUtility::TextWord::addChar(const CHAR_DESC& desc)
  12. {
  13. mChars.push_back(desc);
  14. calculateWidth();
  15. }
  16. void TextUtility::TextWord::addSpace(UINT32 spaceWidth)
  17. {
  18. mSpaceWidth += spaceWidth;
  19. calculateWidth();
  20. }
  21. void TextUtility::TextWord::removeLastChar()
  22. {
  23. if(mChars.size() > 0)
  24. {
  25. mChars.erase(mChars.end() - 1);
  26. calculateWidth();
  27. }
  28. }
  29. void TextUtility::TextWord::calculateWidth()
  30. {
  31. if(isSpacer())
  32. {
  33. mWidth = mSpaceWidth;
  34. mHeight = 0;
  35. return;
  36. }
  37. if(mChars.size() == 0)
  38. {
  39. mWidth = 0;
  40. mHeight = 0;
  41. return;
  42. }
  43. mWidth = 0;
  44. mHeight = 0;
  45. UINT32 kerning = 0;
  46. for(size_t i = 0; i < mChars.size() - 1; i++)
  47. {
  48. mWidth += mChars[i].xAdvance + kerning;
  49. mHeight = std::max(mHeight, mChars[i].height);
  50. kerning = 0;
  51. for(size_t j = 0; j < mChars[i].kerningPairs.size(); j++)
  52. {
  53. if(mChars[i].kerningPairs[j].otherCharId == mChars[i + 1].charId)
  54. {
  55. kerning = mChars[i].kerningPairs[j].amount;
  56. break;
  57. }
  58. }
  59. }
  60. mWidth += mChars[mChars.size() - 1].xAdvance + kerning;
  61. }
  62. TextUtility::TextLine::TextLine(UINT32 baselineOffset, UINT32 lineHeight, UINT32 spaceWidth)
  63. :mWidth(0), mHeight(0), mLastWord(nullptr), mBaselineOffset(baselineOffset), mLineHeight(lineHeight), mSpaceWidth(spaceWidth)
  64. {
  65. }
  66. TextUtility::TextLine::~TextLine()
  67. {
  68. }
  69. void TextUtility::TextLine::add(const CHAR_DESC& charDesc)
  70. {
  71. if(mLastWord == nullptr)
  72. {
  73. mWords.push_back(TextWord(false));
  74. mLastWord = &mWords.back();
  75. mLastWord->addChar(charDesc);
  76. }
  77. else
  78. {
  79. if(mLastWord->isSpacer())
  80. {
  81. mWords.push_back(TextWord(false));
  82. mLastWord = &mWords.back();
  83. }
  84. mLastWord->addChar(charDesc);
  85. }
  86. calculateBounds();
  87. }
  88. void TextUtility::TextLine::addSpace()
  89. {
  90. if(mLastWord == nullptr)
  91. {
  92. mWords.push_back(TextWord(true));
  93. mLastWord = &mWords.back();
  94. mLastWord->addSpace(mSpaceWidth);
  95. }
  96. else
  97. {
  98. mWords.push_back(TextWord(true)); // Each space is counted as its own word, to make certain operations easier
  99. mLastWord = &mWords.back();
  100. mLastWord->addSpace(mSpaceWidth);
  101. }
  102. calculateBounds();
  103. }
  104. void TextUtility::TextLine::addWord(const TextWord& word)
  105. {
  106. mWords.push_back(word);
  107. mLastWord = &mWords.back();
  108. calculateBounds();
  109. }
  110. TextUtility::TextWord TextUtility::TextLine::removeLastWord()
  111. {
  112. if(mWords.size() == 0)
  113. return nullptr;
  114. TextWord word = mWords[mWords.size() - 1];
  115. mWords.erase(mWords.end() - 1);
  116. if(mWords.size() > 0)
  117. mLastWord = &mWords[mWords.size() - 1];
  118. else
  119. mLastWord = nullptr;
  120. calculateBounds();
  121. return word;
  122. }
  123. Vector<UINT32>::type TextUtility::TextLine::getNumQuadsPerPage() const
  124. {
  125. Vector<UINT32>::type quadsPerPage;
  126. for(auto wordIter = mWords.begin(); wordIter != mWords.end(); ++wordIter)
  127. {
  128. if(!wordIter->isSpacer())
  129. {
  130. const Vector<CHAR_DESC>::type& chars = wordIter->getChars();
  131. UINT32 kerning = 0;
  132. for(auto charIter = chars.begin(); charIter != chars.end(); ++charIter)
  133. {
  134. if(charIter->page > (UINT32)quadsPerPage.size())
  135. quadsPerPage.resize(charIter->page + 1);
  136. quadsPerPage[charIter->page]++;
  137. }
  138. }
  139. else
  140. quadsPerPage[0]++;
  141. }
  142. return quadsPerPage;
  143. }
  144. UINT32 TextUtility::TextLine::fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const
  145. {
  146. UINT32 numQuads = 0;
  147. UINT32 penX = 0;
  148. for(auto wordIter = mWords.begin(); wordIter != mWords.end(); ++wordIter)
  149. {
  150. if(wordIter->isSpacer())
  151. {
  152. // We store invisible space quads in the first page. Even though they aren't needed
  153. // for rendering and we could just leave an empty space, they are needed for intersection tests
  154. // for things like determining caret placement and selection areas
  155. if(page == 0)
  156. {
  157. INT32 curX = penX;
  158. INT32 curY = 0;
  159. UINT32 curVert = offset * 4;
  160. UINT32 curIndex = offset * 6;
  161. vertices[curVert + 0] = Vector2((float)curX, (float)curY);
  162. vertices[curVert + 1] = Vector2((float)(curX + mSpaceWidth), (float)curY);
  163. vertices[curVert + 2] = Vector2((float)curX, (float)curY + (float)mLineHeight);
  164. vertices[curVert + 3] = Vector2((float)(curX + mSpaceWidth), (float)curY + (float)mLineHeight);
  165. if(uvs != nullptr)
  166. {
  167. uvs[curVert + 0] = Vector2(0.0f, 0.0f);
  168. uvs[curVert + 1] = Vector2(0.0f, 0.0f);
  169. uvs[curVert + 2] = Vector2(0.0f, 0.0f);
  170. uvs[curVert + 3] = Vector2(0.0f, 0.0f);
  171. }
  172. // Triangles are back-facing which makes them invisible
  173. if(indexes != nullptr)
  174. {
  175. indexes[curIndex + 0] = curVert + 0;
  176. indexes[curIndex + 1] = curVert + 2;
  177. indexes[curIndex + 2] = curVert + 1;
  178. indexes[curIndex + 3] = curVert + 1;
  179. indexes[curIndex + 4] = curVert + 2;
  180. indexes[curIndex + 5] = curVert + 3;
  181. }
  182. offset++;
  183. numQuads++;
  184. if(offset > size)
  185. CM_EXCEPT(InternalErrorException, "Out of buffer bounds. Buffer size: " + toString(size));
  186. }
  187. penX += mSpaceWidth;
  188. }
  189. else
  190. {
  191. const Vector<CHAR_DESC>::type& chars = wordIter->getChars();
  192. UINT32 kerning = 0;
  193. for(auto charIter = chars.begin(); charIter != chars.end(); ++charIter)
  194. {
  195. INT32 curX = penX + charIter->xOffset;
  196. INT32 curY = ((INT32)mBaselineOffset - charIter->yOffset);
  197. penX += charIter->xAdvance + kerning;
  198. kerning = 0;
  199. if((charIter + 1) != chars.end())
  200. {
  201. for(size_t j = 0; j < charIter->kerningPairs.size(); j++)
  202. {
  203. if(charIter->kerningPairs[j].otherCharId == (charIter + 1)->charId)
  204. {
  205. kerning = charIter->kerningPairs[j].amount;
  206. break;
  207. }
  208. }
  209. }
  210. if(charIter->page != page)
  211. continue;
  212. UINT32 curVert = offset * 4;
  213. UINT32 curIndex = offset * 6;
  214. vertices[curVert + 0] = Vector2((float)curX, (float)curY);
  215. vertices[curVert + 1] = Vector2((float)(curX + charIter->width), (float)curY);
  216. vertices[curVert + 2] = Vector2((float)curX, (float)curY + (float)charIter->height);
  217. vertices[curVert + 3] = Vector2((float)(curX + charIter->width), (float)curY + (float)charIter->height);
  218. if(uvs != nullptr)
  219. {
  220. uvs[curVert + 0] = Vector2(charIter->uvX, charIter->uvY);
  221. uvs[curVert + 1] = Vector2(charIter->uvX + charIter->uvWidth, charIter->uvY);
  222. uvs[curVert + 2] = Vector2(charIter->uvX, charIter->uvY + charIter->uvHeight);
  223. uvs[curVert + 3] = Vector2(charIter->uvX + charIter->uvWidth, charIter->uvY + charIter->uvHeight);
  224. }
  225. if(indexes != nullptr)
  226. {
  227. indexes[curIndex + 0] = curVert + 0;
  228. indexes[curIndex + 1] = curVert + 1;
  229. indexes[curIndex + 2] = curVert + 2;
  230. indexes[curIndex + 3] = curVert + 1;
  231. indexes[curIndex + 4] = curVert + 3;
  232. indexes[curIndex + 5] = curVert + 2;
  233. }
  234. offset++;
  235. numQuads++;
  236. if(offset > size)
  237. CM_EXCEPT(InternalErrorException, "Out of buffer bounds. Buffer size: " + toString(size));
  238. }
  239. }
  240. }
  241. return numQuads;
  242. }
  243. UINT32 TextUtility::TextLine::getNumChars() const
  244. {
  245. UINT32 numChars = 0;
  246. for(auto& word : mWords)
  247. {
  248. if(word.isSpacer())
  249. numChars++;
  250. else
  251. numChars += (UINT32)word.getChars().size();
  252. }
  253. return numChars;
  254. }
  255. void TextUtility::TextLine::calculateBounds()
  256. {
  257. mWidth = 0;
  258. mHeight = 0;
  259. for(auto iter = mWords.begin(); iter != mWords.end(); ++iter)
  260. {
  261. mWidth += iter->getWidth();
  262. mHeight = std::max(mHeight, iter->getHeight());
  263. }
  264. }
  265. TextUtility::TextData::~TextData()
  266. {
  267. }
  268. std::shared_ptr<TextUtility::TextData> TextUtility::getTextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width, UINT32 height, bool wordWrap)
  269. {
  270. const FontData* fontData = nullptr;
  271. if(font != nullptr)
  272. {
  273. UINT32 nearestSize = font->getClosestAvailableSize(fontSize);
  274. fontData = font->getFontDataForSize(nearestSize);
  275. }
  276. if(fontData == nullptr || fontData->texturePages.size() == 0)
  277. return nullptr;
  278. if(fontData->size != fontSize)
  279. {
  280. LOGWRN("Unable to find font with specified size (" + toString(fontSize) + "). Using nearest available size: " + toString(fontData->size));
  281. }
  282. bool heightIsLimited = height > 0;
  283. bool widthIsLimited = width > 0;
  284. std::shared_ptr<TextUtility::TextData> textData = cm_shared_ptr<TextData, PoolAlloc>();
  285. textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
  286. TextLine* curLine = &textData->mLines.back();
  287. UINT32 curHeight = fontData->fontDesc.lineHeight;
  288. UINT32 charIdx = 0;
  289. while(true)
  290. {
  291. if(charIdx >= text.size())
  292. break;
  293. if(text[charIdx] == '\n')
  294. {
  295. if(heightIsLimited && (curHeight + fontData->fontDesc.lineHeight * 2) > height)
  296. break; // Max height reached
  297. textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
  298. curLine = &textData->mLines.back();
  299. curHeight += fontData->fontDesc.lineHeight;
  300. charIdx++;
  301. continue;
  302. }
  303. UINT32 charId = text[charIdx];
  304. const CHAR_DESC& charDesc = fontData->getCharDesc(charId);
  305. if(charId != SPACE_CHAR)
  306. {
  307. curLine->add(charDesc);
  308. if(charDesc.page >= (UINT32)textData->mQuadsPerPage.size())
  309. {
  310. textData->mQuadsPerPage.resize(charDesc.page + 1);
  311. textData->mTexturePages.resize(charDesc.page + 1);
  312. }
  313. textData->mQuadsPerPage[charDesc.page]++;
  314. if(textData->mTexturePages[charDesc.page] == nullptr)
  315. textData->mTexturePages[charDesc.page] = fontData->texturePages[charDesc.page];
  316. }
  317. else
  318. {
  319. curLine->addSpace();
  320. if((UINT32)textData->mQuadsPerPage.size() == 0)
  321. {
  322. textData->mQuadsPerPage.resize(1);
  323. textData->mTexturePages.resize(1);
  324. }
  325. textData->mQuadsPerPage[0]++;
  326. if(textData->mTexturePages[0] == nullptr)
  327. textData->mTexturePages[0] = fontData->texturePages[0];
  328. }
  329. if(widthIsLimited && curLine->getWidth() > width)
  330. {
  331. if(wordWrap)
  332. {
  333. TextWord lastWord = curLine->removeLastWord();
  334. if(lastWord.isSpacer())
  335. curLine->addWord(lastWord); // Spaces can stay on previous line even if they don't technically fit
  336. // No more lines fit vertically so we're done
  337. if(heightIsLimited && curHeight > height)
  338. break;
  339. textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
  340. curLine = &textData->mLines.back();
  341. curHeight += fontData->fontDesc.lineHeight;
  342. if(!lastWord.isSpacer())
  343. curLine->addWord(lastWord);
  344. }
  345. else
  346. {
  347. // Nothing else we can do, chars don't fit so we are done
  348. break;
  349. }
  350. }
  351. charIdx++;
  352. }
  353. return textData;
  354. }
  355. UINT32 TextUtility::TextData::getWidth() const
  356. {
  357. UINT32 width = 0;
  358. for(auto& line : mLines)
  359. width = std::max(width, line.getWidth());
  360. return width;
  361. }
  362. UINT32 TextUtility::TextData::getHeight() const
  363. {
  364. UINT32 height = 0;
  365. for(auto& line : mLines)
  366. height += line.getHeight();
  367. return height;
  368. }
  369. }