You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TextRasterizer.cpp 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. #include "TextRasterizer.h"
  2. #include <string>
  3. #include <cmath>
  4. #include <limits>
  5. #include <iostream>
  6. #include "../../environment/Environment.h"
  7. TextRasterizer::TextRasterizer(const std::string &fontPath, const int size, const unsigned int resolution, const bool bold) {
  8. fontSize = size;
  9. FT_Init_FreeType(&lib);
  10. face = std::make_unique<FT_Face>();
  11. std::string path = Environment::getResourceDir() + "/" + fontPath;
  12. if (FT_New_Face(lib, bold ? (path.substr(0, fontPath.length() - 4) + "-Bold.ttf").c_str() : path.c_str(), 0, face.get())) {
  13. std::cout << "Could not open font" << std::endl;
  14. return;
  15. }
  16. if (!isUnicodeBMP(*face)) {
  17. std::cout << "Font is not Unicode BMP" << std::endl;
  18. return;
  19. }
  20. if (FT_Set_Char_Size(*face, 0, fontSize * 64, resolution, resolution)) {
  21. std::cout << "Could not set font size" << std::endl;
  22. return;
  23. }
  24. font = hb_ft_font_create(*face, nullptr);
  25. buffer = hb_buffer_create();
  26. if (!hb_buffer_allocation_successful(buffer)) {
  27. std::cout << "Could not create HarfBuzz buffer" << std::endl;
  28. return;
  29. }
  30. }
  31. TextRasterizer::~TextRasterizer() {
  32. hb_buffer_destroy(buffer);
  33. hb_font_destroy(font);
  34. FT_Done_FreeType(lib);
  35. }
  36. std::unique_ptr<std::pair<int, int>> TextRasterizer::size(const rasterizationRequest &request) const {
  37. if (!request.availableWidth) {
  38. // window is likely minimized, so no output
  39. return nullptr;
  40. }
  41. if (request.startX == request.availableWidth) {
  42. std::cout << "TextRasterizer::size - x [" << static_cast<int>(request.startX) << "] matches window width [" << static_cast<int>(request.availableWidth)<< "] for text[" << request.text << "] no room to render anything" << std::endl;
  43. return nullptr;
  44. }
  45. if (request.startX > request.availableWidth) {
  46. std::cout << "TextRasterizer::size - x [" << static_cast<int>(request.startX) << "] requested outside of window width [" << static_cast<int>(request.availableWidth)<< "] for text[" << request.text << "]" << std::endl;
  47. return nullptr;
  48. }
  49. unsigned int glyphCount;
  50. hb_buffer_reset(buffer);
  51. hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
  52. hb_buffer_set_language(buffer, hb_language_from_string("en", 2));
  53. hb_buffer_add_utf8(buffer, request.text.c_str(), request.text.length(), 0, request.text.length());
  54. #pragma GCC diagnostic push
  55. #pragma GCC diagnostic ignored "-Wold-style-cast"
  56. const hb_feature_t KerningOn = { HB_TAG('k', 'e', 'r', 'n'), 1, 0, std::numeric_limits<unsigned int>::max() };
  57. #pragma GCC diagnostic pop
  58. hb_shape(font, buffer, &KerningOn, 1);
  59. const hb_glyph_info_t *glyphInfo = hb_buffer_get_glyph_infos(buffer, &glyphCount);
  60. const hb_glyph_position_t *glyphPos = hb_buffer_get_glyph_positions(buffer, &glyphCount);
  61. std::unique_ptr<rasterizationResponse> response = std::make_unique<rasterizationResponse>();
  62. // figure out width/height
  63. int cx = 0;
  64. int cy = 0;
  65. int xmax = 0;
  66. int y0max = 0, y1max = 0;
  67. int lines = 1;
  68. response->wrapped = false;
  69. int lineXStart = request.startX;
  70. int leftPadding = 0;
  71. int wrapToX = 0;
  72. for (unsigned int i = 0; i < glyphCount; i++) {
  73. if (FT_Load_Glyph(*face, glyphInfo[i].codepoint, FT_LOAD_DEFAULT)) {
  74. std::cout << "Could not load glyph" << std::endl;
  75. return nullptr;
  76. }
  77. const FT_GlyphSlot slot = (*face)->glyph;
  78. if (FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL)) {
  79. std::cout << "Could not render glyph" << std::endl;
  80. return nullptr;
  81. }
  82. // how much space does this character take
  83. const float xa = static_cast<float>(glyphPos[i].x_advance) / 64;
  84. const float ya = static_cast<float>(glyphPos[i].y_advance) / 64; //mostly 0s
  85. // do we need to padding the texture to the left for any lines
  86. if (cx == 0) {
  87. if (slot->bitmap_left < 0) {
  88. // figure out max amount of padding we need
  89. leftPadding = std::max(leftPadding, -slot->bitmap_left);
  90. }
  91. }
  92. // wrap to next line on width
  93. if (cx + lineXStart >= request.availableWidth) {
  94. response->wrapped = true;
  95. if (request.noWrap) {
  96. glyphCount = i;
  97. } else {
  98. //std::cout << "multine text: [" << text << "] new line:" << cy << " x: " << (int)x << "+ cx:" << (int)cx << std::endl;
  99. //std::cout << "line #" << lines << " starts at " << lineXStart << " ends at " << lineXStart + cx << std::endl;
  100. xmax = request.availableWidth - wrapToX; // the whole width of parent to the edge of windowWidth
  101. //std::cout << "new width: " << xmax << std::endl;
  102. cx = wrapToX; // wrapToX (was 0) (was -= xmax)
  103. cy -= std::ceil(1.2f * fontSize); // 1.2 scalar from https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
  104. lines++;
  105. lineXStart = wrapToX; // change where we start
  106. }
  107. }
  108. //std::cout << "glyph:" << xa << "x" << ya << std::endl;
  109. // update glyph space allocation
  110. cx += xa;
  111. cy += ya; // is normal for y0 at bottom
  112. // update glyph maxes
  113. const FT_Bitmap ftBitmap = slot->bitmap;
  114. const float yo = static_cast<float>(glyphPos[i].y_offset) / 64;
  115. int y0 = static_cast<int>(floor(yo + slot->bitmap_top));
  116. int y1 = y0 + static_cast<int>(ftBitmap.rows);
  117. y0max=std::max(y0max, y0);
  118. y1max=std::max(y1max, y1);
  119. // track new max width
  120. xmax=std::max(xmax, cx);
  121. }
  122. if (leftPadding) {
  123. xmax+=leftPadding; // increase width;
  124. }
  125. // at least one line
  126. cy -= std::ceil(1.2f * fontSize); // 1.2 scalar from https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
  127. response->height = -cy;
  128. response->width = xmax;
  129. //std::cout << "lines: " << lines << " wrapToX: " << wrapToX << " startX: " << x << " xmax: " << xmax << std::endl;
  130. //std::cout << "y1max: " << y1max << " lines: " << lines << std::endl;
  131. y1max *= lines;
  132. //std::cout << "initial:" << (int)width << "x" << (int)height << std::endl;
  133. if (response->height < y1max) {
  134. response->height = y1max;
  135. //std::cout << "adjusted:" << (int)width << "x" << (int)height << std::endl;
  136. }
  137. // FIXME: how about endingX,Y?
  138. return std::make_unique<std::pair<int, int>>(std::pair<int, int>(response->width, response->height));
  139. }
  140. #include <GL/glew.h>
  141. std::unique_ptr<rasterizationResponse> TextRasterizer::rasterize(const rasterizationRequest &request) const {
  142. if (request.startX == request.availableWidth) {
  143. std::cout << "TextRasterizer::rasterize - x [" << static_cast<int>(request.startX) << "] matches window width [" << static_cast<int>(request.availableWidth)<< "] for text[" << request.text << "] no room to render anything" << std::endl;
  144. return nullptr;
  145. }
  146. if (request.startX > request.availableWidth) {
  147. std::cout << "TextRasterizer::rasterize - x [" << static_cast<int>(request.startX) << "] requested outside of window width [" << static_cast<int>(request.availableWidth)<< "] for text[" << request.text << "]" << std::endl;
  148. return nullptr;
  149. }
  150. unsigned int glyphCount;
  151. hb_buffer_reset(buffer);
  152. hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
  153. hb_buffer_set_language(buffer, hb_language_from_string("en", 2));
  154. hb_buffer_add_utf8(buffer, request.text.c_str(), request.text.length(), 0, request.text.length());
  155. #pragma GCC diagnostic push
  156. #pragma GCC diagnostic ignored "-Wold-style-cast"
  157. const hb_feature_t KerningOn = { HB_TAG('k', 'e', 'r', 'n'), 1, 0, std::numeric_limits<unsigned int>::max() };
  158. #pragma GCC diagnostic pop
  159. hb_shape(font, buffer, &KerningOn, 1);
  160. const hb_glyph_info_t *glyphInfo = hb_buffer_get_glyph_infos(buffer, &glyphCount);
  161. const hb_glyph_position_t *glyphPos = hb_buffer_get_glyph_positions(buffer, &glyphCount);
  162. std::unique_ptr<rasterizationResponse> response = std::make_unique<rasterizationResponse>();
  163. // figure out width/height
  164. int cx = 0;
  165. int cy = 0;
  166. int xmax = 0;
  167. int y0max = 0, y1max = 0;
  168. int lines = 1;
  169. response->wrapped = false;
  170. int lineXStart = request.startX;
  171. int leftPadding = 0;
  172. int wrapToX = 0;
  173. for (unsigned int i = 0; i < glyphCount; i++) {
  174. if (FT_Load_Glyph(*face, glyphInfo[i].codepoint, FT_LOAD_DEFAULT)) {
  175. std::cout << "Could not load glyph" << std::endl;
  176. return nullptr;
  177. }
  178. const FT_GlyphSlot slot = (*face)->glyph;
  179. if (FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL)) {
  180. std::cout << "Could not render glyph" << std::endl;
  181. return nullptr;
  182. }
  183. // how much space does this character take
  184. const float xa = static_cast<float>(glyphPos[i].x_advance) / 64;
  185. const float ya = static_cast<float>(glyphPos[i].y_advance) / 64; //mostly 0s
  186. // do we need to padding the texture to the left for any lines
  187. if (cx == 0) {
  188. if (slot->bitmap_left < 0) {
  189. // figure out max amount of padding we need
  190. leftPadding = std::max(leftPadding, -slot->bitmap_left);
  191. }
  192. }
  193. // wrap to next line on width
  194. if ((cx + lineXStart) - request.sourceStartX >= request.availableWidth) {
  195. response->wrapped = true;
  196. if (request.noWrap) {
  197. std::cout << "TextRasterizer::rasterize - we've hit wrap & noWrap at " << cx << " + " << lineXStart << " < " << request.availableWidth << std::endl;
  198. // we can only fit partial of the last char
  199. std::cout << "TextRasterizer::rasterize - truncating " << glyphCount << " char(s) to " << i << " char(s)" << std::endl;
  200. glyphCount = i;
  201. // we're done
  202. break;
  203. } else {
  204. //std::cout << "multine text: [" << text << "] new line:" << cy << " x: " << (int)x << "+ cx:" << (int)cx << std::endl;
  205. //std::cout << "line #" << lines << " starts at " << lineXStart << " ends at " << lineXStart + cx << std::endl;
  206. xmax = request.availableWidth - wrapToX; // the whole width of parent to the edge of windowWidth
  207. //std::cout << "new width: " << xmax << std::endl;
  208. cx = wrapToX; // wrapToX (was 0) (was -= xmax)
  209. cy -= std::ceil(1.2f * fontSize); // 1.2 scalar from https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
  210. lines++;
  211. lineXStart = wrapToX; // change where we start
  212. }
  213. }
  214. //std::cout << "glyph:" << xa << "x" << ya << std::endl;
  215. // update glyph space allocation
  216. cx += xa;
  217. cy += ya; // is normal for y0 at bottom
  218. // update glyph maxes
  219. const FT_Bitmap ftBitmap = slot->bitmap;
  220. const float yo = static_cast<float>(glyphPos[i].y_offset) / 64;
  221. int y0 = static_cast<int>(floor(yo + slot->bitmap_top));
  222. int y1 = y0 + static_cast<int>(ftBitmap.rows);
  223. y0max=std::max(y0max, y0);
  224. y1max=std::max(y1max, y1);
  225. // track new max width
  226. xmax=std::max(xmax, cx);
  227. }
  228. //std::cout << "TextRasterizer::rasterize - xmax exits loop at " << xmax << std::endl;
  229. if (leftPadding) {
  230. //std::cout << "TextRasterizer::rasterize - leftPadding " << leftPadding << std::endl;
  231. xmax += leftPadding; // increase width;
  232. }
  233. // at least one line
  234. cy -= std::ceil(1.2f * fontSize); // 1.2 scalar from https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
  235. //std::cout << "TextRasterizer::rasterize - initial width " << xmax << std::endl;
  236. response->width = xmax;
  237. response->height = -cy;
  238. //std::cout << "lines: " << lines << " wrapToX: " << wrapToX << " startX: " << x << " xmax: " << xmax << std::endl;
  239. //std::cout << "y1max: " << y1max << " lines: " << lines << std::endl;
  240. y1max *= lines;
  241. //std::cout << "initial:" << (int)width << "x" << (int)height << std::endl;
  242. if (response->height < y1max) {
  243. response->height = y1max;
  244. //std::cout << "adjusted:" << (int)width << "x" << (int)height << std::endl;
  245. }
  246. // adjust sourceStart
  247. //std::cout << "TextRasterizer::rasterize - adjust sourceStart: " << request.sourceStartX << "," << request.sourceStartY << std::endl;
  248. response->width -= request.sourceStartX;
  249. response->height -= request.sourceStartY;
  250. //std::cout << "TextRasterizer::rasterize - final:" << static_cast<int>(response->width) << "x" << static_cast<int>(response->height) << std::endl;
  251. /*
  252. if (xmax==windowWidth - x) {
  253. std::cout << "Wrapped text[" << text << "] over " << lines << " lines " << xmax << "x" << static_cast<int>(height) << std::endl;
  254. }
  255. */
  256. //std::cout << "text size: " << (int)width << "x" << (int)height << std::endl;
  257. response->textureWidth = pow(2, ceil(log(response->width) / log(2)));
  258. response->textureHeight = pow(2, ceil(log(response->height) / log(2)));
  259. //std::cout << "text texture size:" << line->textureWidth << "x" << line->textureHeight << std::endl;
  260. size_t size = static_cast<size_t>(response->textureWidth * response->textureHeight); // here?
  261. response->textureData = std::make_unique<unsigned char[]>(size); // here?
  262. if (!response->textureData) {
  263. std::cout << "failed to create texture" << static_cast<int>(response->width) << "X" << static_cast<int>(response->height) << std::endl;
  264. return nullptr;
  265. }
  266. // translation information
  267. response->x0 = -leftPadding; // wrap to element start (wrapToX (0) or x)
  268. response->y0 = 0;
  269. response->x1 = -leftPadding + response->width;
  270. response->y1 = -response->height;
  271. //std::cout << "xd: " << static_cast<int>(response->x1-response->x0) << " yd: " << static_cast<int>(response->y0-response->y1) << std::endl;
  272. //std::cout << "Requesting texture of " << response->textureWidth << "x" << response->textureHeight << "max: " << win->maxTextureSize << std::endl;
  273. if (response->textureHeight > static_cast<unsigned int>(win->maxTextureSize)) {
  274. std::cout << "Truncating text texture to fit in your video card" << std::endl;
  275. response->textureHeight = static_cast<unsigned int>(win->maxTextureSize);
  276. }
  277. // texture coords
  278. response->s0 = 0.0f;
  279. response->t0 = 0.0f;
  280. response->s1 = response->width / response->textureWidth;
  281. response->t1 = response->height / response->textureHeight;
  282. //std::cout << "s1: " << response->s1 << " t1: " << response->t1 << std::endl;
  283. // copy all glyphs into one single glyph
  284. // still neds to start at X
  285. cx = response->wrapped ? request.startX : 0; // reset
  286. cy = 0;
  287. //std::cout << "[" << request.text << "] wrapped? " << response->wrapped << " starting at: " << cx << std::endl;
  288. //int miny0 = 99;
  289. int maxy0 = 0;
  290. for (unsigned int i = 0; i < glyphCount; i++) {
  291. if (FT_Load_Glyph(*face, glyphInfo[i].codepoint, FT_LOAD_DEFAULT)) {
  292. std::cout << "Could not load glyph" << std::endl;
  293. return nullptr;
  294. }
  295. const FT_GlyphSlot slot = (*face)->glyph;
  296. if (FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL)) {
  297. std::cout << "Could not render glyph" << std::endl;
  298. return nullptr;
  299. }
  300. const FT_Bitmap ftBitmap = slot->bitmap;
  301. // figure out glyph starting point
  302. const float yo = static_cast<float>(glyphPos[i].y_offset) / 64;
  303. int y0 = static_cast<int>(floor(yo + slot->bitmap_top));
  304. //miny0 = std::min(y0, miny0);
  305. maxy0 = std::max(y0, maxy0);
  306. int bump = y0max - y0; // Y adjust for this glyph
  307. const float xa = static_cast<float>(glyphPos[i].x_advance) / 64;
  308. // if this char is too width for this line, advance to next line
  309. if (!request.noWrap && cx + xa >= request.availableWidth) {
  310. cx = wrapToX;
  311. cy += std::ceil(1.2f * fontSize);// 1.2 scalar from https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
  312. //std::cout << "textWrap - cx reset to: " << cx << " cy is now: " << cy << std::endl;
  313. }
  314. //std::cout << "placing glyph[" << text[i] << "] at " << cx << " cy is now: " << cy << std::endl;
  315. if (cx < request.sourceStartX) {
  316. // skip ahead
  317. cx += xa;
  318. continue;
  319. }
  320. // place glyph bitmap data into texture
  321. for (unsigned int iy = 0; iy < ftBitmap.rows; iy++) { // line by line
  322. // source is 0 to (0:iy:rows)
  323. // dest is cx+bl, (0:iy:rows)+(0:cy:height)+bump
  324. //std::cout << "placing glyph row at " << (cx + slot->bitmap_left) << "x" << ((iy + cy) + bump) << std::endl;
  325. unsigned int destPos = static_cast<unsigned int>(cx - request.sourceStartX + leftPadding + slot->bitmap_left) + ((iy + static_cast<unsigned int>(cy - request.sourceStartY)) + static_cast<unsigned int>(bump)) * response->textureWidth;
  326. if (destPos >= size) {
  327. // we're done with this line
  328. continue;
  329. }
  330. unsigned char *src = ftBitmap.buffer + iy * static_cast<unsigned int>(ftBitmap.width);
  331. unsigned char *dest = response->textureData.get() + destPos;
  332. for(unsigned int ix = 0; ix < ftBitmap.width; ix++) { // pixel by pixel
  333. if (destPos + ix >= size) {
  334. // we're done with this line
  335. continue;
  336. }
  337. //std::cout << "putting:" << (int)src[ix] << " over " << (int)dest[ix] << std::endl;
  338. if (src[ix] && src[ix]>dest[ix]) {
  339. dest[ix]=src[ix];
  340. }
  341. }
  342. }
  343. cx += xa;
  344. }
  345. //std::cout << "final size: " << (int)width << "x" << (int)height << std::endl;
  346. //std::cout << "at: " << (int)line->x0 << "x" << (int)line->y0 << " to: " << (int)line->x1 << "x" << (int)line->y1 <<std::endl;
  347. response->endingX = cx - request.sourceStartX; // maybe should be one xa less?
  348. //std::ceil(0.5 * 1.2f * fontSize)+2
  349. // should this be - request.sourceStartY?
  350. response->endingY = cy + maxy0 + request.sourceStartY; // definitely should be one lineheight higher
  351. //std::cout << "ending at " << response->endingX << "," << response->endingY << std::endl;
  352. return response;
  353. }
  354. bool TextRasterizer::isUnicodeBMP(const FT_Face &ftFace) const {
  355. for (int i = 0; i < ftFace->num_charmaps; i++) {
  356. if (((ftFace->charmaps[i]->platform_id == 0) && (ftFace->charmaps[i]->encoding_id == 3)) || ((ftFace->charmaps[i]->platform_id == 3) && (ftFace->charmaps[i]->encoding_id == 1))) {
  357. FT_Set_Charmap(ftFace, ftFace->charmaps[i]);
  358. return true;
  359. }
  360. }
  361. return false;
  362. }