/xbmc/guilib/GUIFontTTF.cpp

http://github.com/xbmc/xbmc · C++ · 995 lines · 746 code · 138 blank · 111 comment · 139 complexity · 0c8dcd99187f3e88b354036bae6372f3 MD5 · raw file

  1. /*
  2. * Copyright (C) 2005-2018 Team Kodi
  3. * This file is part of Kodi - https://kodi.tv
  4. *
  5. * SPDX-License-Identifier: GPL-2.0-or-later
  6. * See LICENSES/README.md for more information.
  7. */
  8. #include "GUIFont.h"
  9. #include "GUIFontTTF.h"
  10. #include "GUIFontManager.h"
  11. #include "Texture.h"
  12. #include "windowing/GraphicContext.h"
  13. #include "ServiceBroker.h"
  14. #include "filesystem/SpecialProtocol.h"
  15. #include "utils/MathUtils.h"
  16. #include "utils/log.h"
  17. #include "rendering/RenderSystem.h"
  18. #include "windowing/WinSystem.h"
  19. #include "URL.h"
  20. #include "filesystem/File.h"
  21. #include "threads/SystemClock.h"
  22. #include <math.h>
  23. #include <memory>
  24. #include <queue>
  25. // stuff for freetype
  26. #include <ft2build.h>
  27. #ifdef TARGET_WINDOWS_STORE
  28. #define generic GenericFromFreeTypeLibrary
  29. #endif
  30. #include FT_FREETYPE_H
  31. #include FT_GLYPH_H
  32. #include FT_OUTLINE_H
  33. #include FT_STROKER_H
  34. #define CHARS_PER_TEXTURE_LINE 20 // number of characters to cache per texture line
  35. #define CHAR_CHUNK 64 // 64 chars allocated at a time (1024 bytes)
  36. #define GLYPH_STRENGTH_BOLD 24
  37. #define GLYPH_STRENGTH_LIGHT -48
  38. class CFreeTypeLibrary
  39. {
  40. public:
  41. CFreeTypeLibrary()
  42. {
  43. m_library = NULL;
  44. }
  45. virtual ~CFreeTypeLibrary()
  46. {
  47. if (m_library)
  48. FT_Done_FreeType(m_library);
  49. }
  50. FT_Face GetFont(const std::string &filename, float size, float aspect, XUTILS::auto_buffer& memoryBuf)
  51. {
  52. // don't have it yet - create it
  53. if (!m_library)
  54. FT_Init_FreeType(&m_library);
  55. if (!m_library)
  56. {
  57. CLog::Log(LOGERROR, "Unable to initialize freetype library");
  58. return NULL;
  59. }
  60. FT_Face face;
  61. // ok, now load the font face
  62. CURL realFile(CSpecialProtocol::TranslatePath(filename));
  63. if (realFile.GetFileName().empty())
  64. return NULL;
  65. memoryBuf.clear();
  66. #ifndef TARGET_WINDOWS
  67. if (!realFile.GetProtocol().empty())
  68. #endif // ! TARGET_WINDOWS
  69. {
  70. // load file into memory if it is not on local drive
  71. // in case of win32: always load file into memory as filename is in UTF-8,
  72. // but freetype expect filename in ANSI encoding
  73. XFILE::CFile f;
  74. if (f.LoadFile(realFile, memoryBuf) <= 0)
  75. return NULL;
  76. if (FT_New_Memory_Face(m_library, (const FT_Byte*)memoryBuf.get(), memoryBuf.size(), 0, &face) != 0)
  77. return NULL;
  78. }
  79. #ifndef TARGET_WINDOWS
  80. else if (FT_New_Face( m_library, realFile.GetFileName().c_str(), 0, &face ))
  81. return NULL;
  82. #endif // ! TARGET_WINDOWS
  83. unsigned int ydpi = 72; // 72 points to the inch is the freetype default
  84. unsigned int xdpi = (unsigned int)MathUtils::round_int(ydpi * aspect);
  85. // we set our screen res currently to 96dpi in both directions (windows default)
  86. // we cache our characters (for rendering speed) so it's probably
  87. // not a good idea to allow free scaling of fonts - rather, just
  88. // scaling to pixel ratio on screen perhaps?
  89. if (FT_Set_Char_Size( face, 0, (int)(size*64 + 0.5f), xdpi, ydpi ))
  90. {
  91. FT_Done_Face(face);
  92. return NULL;
  93. }
  94. return face;
  95. };
  96. FT_Stroker GetStroker()
  97. {
  98. if (!m_library)
  99. return NULL;
  100. FT_Stroker stroker;
  101. if (FT_Stroker_New(m_library, &stroker))
  102. return NULL;
  103. return stroker;
  104. };
  105. static void ReleaseFont(FT_Face face)
  106. {
  107. assert(face);
  108. FT_Done_Face(face);
  109. };
  110. static void ReleaseStroker(FT_Stroker stroker)
  111. {
  112. assert(stroker);
  113. FT_Stroker_Done(stroker);
  114. }
  115. private:
  116. FT_Library m_library;
  117. };
  118. XBMC_GLOBAL_REF(CFreeTypeLibrary, g_freeTypeLibrary); // our freetype library
  119. #define g_freeTypeLibrary XBMC_GLOBAL_USE(CFreeTypeLibrary)
  120. CGUIFontTTFBase::CGUIFontTTFBase(const std::string& strFileName) : m_staticCache(*this), m_dynamicCache(*this)
  121. {
  122. m_texture = NULL;
  123. m_char = NULL;
  124. m_maxChars = 0;
  125. m_nestedBeginCount = 0;
  126. m_vertex.reserve(4*1024);
  127. m_face = NULL;
  128. m_stroker = NULL;
  129. memset(m_charquick, 0, sizeof(m_charquick));
  130. m_strFileName = strFileName;
  131. m_referenceCount = 0;
  132. m_originX = m_originY = 0.0f;
  133. m_cellBaseLine = m_cellHeight = 0;
  134. m_numChars = 0;
  135. m_posX = m_posY = 0;
  136. m_textureHeight = m_textureWidth = 0;
  137. m_textureScaleX = m_textureScaleY = 0.0;
  138. m_ellipsesWidth = m_height = 0.0f;
  139. m_color = 0;
  140. m_nTexture = 0;
  141. m_renderSystem = CServiceBroker::GetRenderSystem();
  142. }
  143. CGUIFontTTFBase::~CGUIFontTTFBase(void)
  144. {
  145. Clear();
  146. }
  147. void CGUIFontTTFBase::AddReference()
  148. {
  149. m_referenceCount++;
  150. }
  151. void CGUIFontTTFBase::RemoveReference()
  152. {
  153. // delete this object when it's reference count hits zero
  154. m_referenceCount--;
  155. if (!m_referenceCount)
  156. g_fontManager.FreeFontFile(this);
  157. }
  158. void CGUIFontTTFBase::ClearCharacterCache()
  159. {
  160. delete(m_texture);
  161. DeleteHardwareTexture();
  162. m_texture = NULL;
  163. delete[] m_char;
  164. m_char = new Character[CHAR_CHUNK];
  165. memset(m_charquick, 0, sizeof(m_charquick));
  166. m_numChars = 0;
  167. m_maxChars = CHAR_CHUNK;
  168. // set the posX and posY so that our texture will be created on first character write.
  169. m_posX = m_textureWidth;
  170. m_posY = -(int)GetTextureLineHeight();
  171. m_textureHeight = 0;
  172. }
  173. void CGUIFontTTFBase::Clear()
  174. {
  175. delete(m_texture);
  176. m_texture = NULL;
  177. delete[] m_char;
  178. memset(m_charquick, 0, sizeof(m_charquick));
  179. m_char = NULL;
  180. m_maxChars = 0;
  181. m_numChars = 0;
  182. m_posX = 0;
  183. m_posY = 0;
  184. m_nestedBeginCount = 0;
  185. if (m_face)
  186. g_freeTypeLibrary.ReleaseFont(m_face);
  187. m_face = NULL;
  188. if (m_stroker)
  189. g_freeTypeLibrary.ReleaseStroker(m_stroker);
  190. m_stroker = NULL;
  191. m_vertexTrans.clear();
  192. m_vertex.clear();
  193. m_strFileName.clear();
  194. m_fontFileInMemory.clear();
  195. }
  196. bool CGUIFontTTFBase::Load(const std::string& strFilename, float height, float aspect, float lineSpacing, bool border)
  197. {
  198. // we now know that this object is unique - only the GUIFont objects are non-unique, so no need
  199. // for reference tracking these fonts
  200. m_face = g_freeTypeLibrary.GetFont(strFilename, height, aspect, m_fontFileInMemory);
  201. if (!m_face)
  202. return false;
  203. /*
  204. the values used are described below
  205. XBMC coords Freetype coords
  206. 0 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ bbox.yMax, ascender
  207. A \
  208. A A |
  209. A A |
  210. AAAAA pppp cellAscender
  211. A A p p |
  212. A A p p |
  213. m_cellBaseLine _ _A_ _A_ pppp_ _ _ _ _/_ _ _ _ _ 0, base line.
  214. p \
  215. p cellDescender
  216. m_cellHeight _ _ _ _ _ p _ _ _ _ _ _/_ _ _ _ _ bbox.yMin, descender
  217. */
  218. int cellDescender = std::min<int>(m_face->bbox.yMin, m_face->descender);
  219. int cellAscender = std::max<int>(m_face->bbox.yMax, m_face->ascender);
  220. if (border)
  221. {
  222. /*
  223. add on the strength of any border - the non-bordered font needs
  224. aligning with the bordered font by utilising GetTextBaseLine()
  225. */
  226. FT_Pos strength = FT_MulFix( m_face->units_per_EM, m_face->size->metrics.y_scale) / 12;
  227. if (strength < 128)
  228. strength = 128;
  229. cellDescender -= strength;
  230. cellAscender += strength;
  231. m_stroker = g_freeTypeLibrary.GetStroker();
  232. if (m_stroker)
  233. FT_Stroker_Set(m_stroker, strength, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
  234. }
  235. // scale to pixel sizing, rounding so that maximal extent is obtained
  236. float scaler = height / m_face->units_per_EM;
  237. cellDescender = MathUtils::round_int(cellDescender * scaler - 0.5f); // round down
  238. cellAscender = MathUtils::round_int(cellAscender * scaler + 0.5f); // round up
  239. m_cellBaseLine = cellAscender;
  240. m_cellHeight = cellAscender - cellDescender;
  241. m_height = height;
  242. delete(m_texture);
  243. m_texture = NULL;
  244. delete[] m_char;
  245. m_char = NULL;
  246. m_maxChars = 0;
  247. m_numChars = 0;
  248. m_strFilename = strFilename;
  249. m_textureHeight = 0;
  250. m_textureWidth = ((m_cellHeight * CHARS_PER_TEXTURE_LINE) & ~63) + 64;
  251. m_textureWidth = CBaseTexture::PadPow2(m_textureWidth);
  252. if (m_textureWidth > m_renderSystem->GetMaxTextureSize())
  253. m_textureWidth = m_renderSystem->GetMaxTextureSize();
  254. m_textureScaleX = 1.0f / m_textureWidth;
  255. // set the posX and posY so that our texture will be created on first character write.
  256. m_posX = m_textureWidth;
  257. m_posY = -(int)GetTextureLineHeight();
  258. // cache the ellipses width
  259. Character *ellipse = GetCharacter(L'.');
  260. if (ellipse) m_ellipsesWidth = ellipse->advance;
  261. return true;
  262. }
  263. void CGUIFontTTFBase::Begin()
  264. {
  265. if (m_nestedBeginCount == 0 && m_texture != NULL && FirstBegin())
  266. {
  267. m_vertexTrans.clear();
  268. m_vertex.clear();
  269. }
  270. // Keep track of the nested begin/end calls.
  271. m_nestedBeginCount++;
  272. }
  273. void CGUIFontTTFBase::End()
  274. {
  275. if (m_nestedBeginCount == 0)
  276. return;
  277. if (--m_nestedBeginCount > 0)
  278. return;
  279. LastEnd();
  280. }
  281. void CGUIFontTTFBase::DrawTextInternal(float x, float y, const std::vector<UTILS::Color> &colors, const vecText &text, uint32_t alignment, float maxPixelWidth, bool scrolling)
  282. {
  283. if (text.empty())
  284. {
  285. return;
  286. }
  287. Begin();
  288. uint32_t rawAlignment = alignment;
  289. bool dirtyCache(false);
  290. bool hardwareClipping = m_renderSystem->ScissorsCanEffectClipping();
  291. CGUIFontCacheStaticPosition staticPos(x, y);
  292. CGUIFontCacheDynamicPosition dynamicPos;
  293. if (hardwareClipping)
  294. {
  295. dynamicPos = CGUIFontCacheDynamicPosition(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(x, y),
  296. CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(x, y),
  297. CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(x, y));
  298. }
  299. CVertexBuffer unusedVertexBuffer;
  300. CVertexBuffer &vertexBuffer = hardwareClipping ?
  301. m_dynamicCache.Lookup(dynamicPos,
  302. colors, text,
  303. alignment, maxPixelWidth,
  304. scrolling,
  305. XbmcThreads::SystemClockMillis(),
  306. dirtyCache) :
  307. unusedVertexBuffer;
  308. std::shared_ptr<std::vector<SVertex> > tempVertices = std::make_shared<std::vector<SVertex> >();
  309. std::shared_ptr<std::vector<SVertex> > &vertices = hardwareClipping ?
  310. tempVertices :
  311. static_cast<std::shared_ptr<std::vector<SVertex> >&>(m_staticCache.Lookup(staticPos,
  312. colors, text,
  313. alignment, maxPixelWidth,
  314. scrolling,
  315. XbmcThreads::SystemClockMillis(),
  316. dirtyCache));
  317. if (dirtyCache)
  318. {
  319. // save the origin, which is scaled separately
  320. m_originX = x;
  321. m_originY = y;
  322. // Check if we will really need to truncate or justify the text
  323. if ( alignment & XBFONT_TRUNCATED )
  324. {
  325. if ( maxPixelWidth <= 0.0f || GetTextWidthInternal(text.begin(), text.end()) <= maxPixelWidth)
  326. alignment &= ~XBFONT_TRUNCATED;
  327. }
  328. else if ( alignment & XBFONT_JUSTIFIED )
  329. {
  330. if ( maxPixelWidth <= 0.0f )
  331. alignment &= ~XBFONT_JUSTIFIED;
  332. }
  333. // calculate sizing information
  334. float startX = 0;
  335. float startY = (alignment & XBFONT_CENTER_Y) ? -0.5f*m_cellHeight : 0; // vertical centering
  336. if ( alignment & (XBFONT_RIGHT | XBFONT_CENTER_X) )
  337. {
  338. // Get the extent of this line
  339. float w = GetTextWidthInternal( text.begin(), text.end() );
  340. if ( alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f ) // + 0.5f due to rounding issues
  341. w = maxPixelWidth;
  342. if ( alignment & XBFONT_CENTER_X)
  343. w *= 0.5f;
  344. // Offset this line's starting position
  345. startX -= w;
  346. }
  347. float spacePerSpaceCharacter = 0; // for justification effects
  348. if ( alignment & XBFONT_JUSTIFIED )
  349. {
  350. // first compute the size of the text to render in both characters and pixels
  351. unsigned int numSpaces = 0;
  352. float linePixels = 0;
  353. for (const auto& pos : text)
  354. {
  355. Character* ch = GetCharacter(pos);
  356. if (ch)
  357. {
  358. if ((pos & 0xffff) == L' ')
  359. numSpaces += 1;
  360. linePixels += ch->advance;
  361. }
  362. }
  363. if (numSpaces > 0)
  364. spacePerSpaceCharacter = (maxPixelWidth - linePixels) / numSpaces;
  365. }
  366. float cursorX = 0; // current position along the line
  367. // Collect all the Character info in a first pass, in case any of them
  368. // are not currently cached and cause the texture to be enlarged, which
  369. // would invalidate the texture coordinates.
  370. std::queue<Character> characters;
  371. if (alignment & XBFONT_TRUNCATED)
  372. GetCharacter(L'.');
  373. for (const auto& pos : text)
  374. {
  375. Character* ch = GetCharacter(pos);
  376. if (!ch)
  377. {
  378. Character null = { 0 };
  379. characters.push(null);
  380. continue;
  381. }
  382. characters.push(*ch);
  383. if (maxPixelWidth > 0 &&
  384. cursorX + ((alignment & XBFONT_TRUNCATED) ? ch->advance + 3 * m_ellipsesWidth : 0) > maxPixelWidth)
  385. break;
  386. cursorX += ch->advance;
  387. }
  388. cursorX = 0;
  389. for (const auto& pos : text)
  390. {
  391. // If starting text on a new line, determine justification effects
  392. // Get the current letter in the CStdString
  393. UTILS::Color color = (pos & 0xff0000) >> 16;
  394. if (color >= colors.size())
  395. color = 0;
  396. color = colors[color];
  397. // grab the next character
  398. Character *ch = &characters.front();
  399. if (ch->letterAndStyle == 0)
  400. {
  401. characters.pop();
  402. continue;
  403. }
  404. if ( alignment & XBFONT_TRUNCATED )
  405. {
  406. // Check if we will be exceeded the max allowed width
  407. if ( cursorX + ch->advance + 3 * m_ellipsesWidth > maxPixelWidth )
  408. {
  409. // Yup. Let's draw the ellipses, then bail
  410. // Perhaps we should really bail to the next line in this case??
  411. Character *period = GetCharacter(L'.');
  412. if (!period)
  413. break;
  414. for (int i = 0; i < 3; i++)
  415. {
  416. RenderCharacter(startX + cursorX, startY, period, color, !scrolling, *tempVertices);
  417. cursorX += period->advance;
  418. }
  419. break;
  420. }
  421. }
  422. else if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
  423. break; // exceeded max allowed width - stop rendering
  424. RenderCharacter(startX + cursorX, startY, ch, color, !scrolling, *tempVertices);
  425. if ( alignment & XBFONT_JUSTIFIED )
  426. {
  427. if ((pos & 0xffff) == L' ')
  428. cursorX += ch->advance + spacePerSpaceCharacter;
  429. else
  430. cursorX += ch->advance;
  431. }
  432. else
  433. cursorX += ch->advance;
  434. characters.pop();
  435. }
  436. if (hardwareClipping)
  437. {
  438. CVertexBuffer &vertexBuffer = m_dynamicCache.Lookup(dynamicPos,
  439. colors, text,
  440. rawAlignment, maxPixelWidth,
  441. scrolling,
  442. XbmcThreads::SystemClockMillis(),
  443. dirtyCache);
  444. CVertexBuffer newVertexBuffer = CreateVertexBuffer(*tempVertices);
  445. vertexBuffer = newVertexBuffer;
  446. m_vertexTrans.emplace_back(0, 0, 0, &vertexBuffer,
  447. CServiceBroker::GetWinSystem()->GetGfxContext().GetClipRegion());
  448. }
  449. else
  450. {
  451. m_staticCache.Lookup(staticPos,
  452. colors, text,
  453. rawAlignment, maxPixelWidth,
  454. scrolling,
  455. XbmcThreads::SystemClockMillis(),
  456. dirtyCache) = *static_cast<CGUIFontCacheStaticValue *>(&tempVertices);
  457. /* Append the new vertices to the set collected since the first Begin() call */
  458. m_vertex.insert(m_vertex.end(), tempVertices->begin(), tempVertices->end());
  459. }
  460. }
  461. else
  462. {
  463. if (hardwareClipping)
  464. m_vertexTrans.emplace_back(dynamicPos.m_x, dynamicPos.m_y, dynamicPos.m_z, &vertexBuffer,
  465. CServiceBroker::GetWinSystem()->GetGfxContext().GetClipRegion());
  466. else
  467. /* Append the vertices from the cache to the set collected since the first Begin() call */
  468. m_vertex.insert(m_vertex.end(), vertices->begin(), vertices->end());
  469. }
  470. End();
  471. }
  472. // this routine assumes a single line (i.e. it was called from GUITextLayout)
  473. float CGUIFontTTFBase::GetTextWidthInternal(vecText::const_iterator start, vecText::const_iterator end)
  474. {
  475. float width = 0;
  476. while (start != end)
  477. {
  478. Character *c = GetCharacter(*start++);
  479. if (c)
  480. {
  481. // If last character in line, we want to add render width
  482. // and not advance distance - this makes sure that italic text isn't
  483. // choped on the end (as render width is larger than advance then).
  484. if (start == end)
  485. width += std::max(c->right - c->left + c->offsetX, c->advance);
  486. else
  487. width += c->advance;
  488. }
  489. }
  490. return width;
  491. }
  492. float CGUIFontTTFBase::GetCharWidthInternal(character_t ch)
  493. {
  494. Character *c = GetCharacter(ch);
  495. if (c) return c->advance;
  496. return 0;
  497. }
  498. float CGUIFontTTFBase::GetTextHeight(float lineSpacing, int numLines) const
  499. {
  500. return (float)(numLines - 1) * GetLineHeight(lineSpacing) + m_cellHeight;
  501. }
  502. float CGUIFontTTFBase::GetLineHeight(float lineSpacing) const
  503. {
  504. if (m_face)
  505. return lineSpacing * m_face->size->metrics.height / 64.0f;
  506. return 0.0f;
  507. }
  508. const unsigned int CGUIFontTTFBase::spacing_between_characters_in_texture = 1;
  509. unsigned int CGUIFontTTFBase::GetTextureLineHeight() const
  510. {
  511. return m_cellHeight + spacing_between_characters_in_texture;
  512. }
  513. CGUIFontTTFBase::Character* CGUIFontTTFBase::GetCharacter(character_t chr)
  514. {
  515. wchar_t letter = (wchar_t)(chr & 0xffff);
  516. character_t style = (chr & 0x7000000) >> 24;
  517. // ignore linebreaks
  518. if (letter == L'\r')
  519. return NULL;
  520. // quick access to ascii chars
  521. if (letter < 255)
  522. {
  523. character_t ch = (style << 8) | letter;
  524. if (ch < LOOKUPTABLE_SIZE && m_charquick[ch])
  525. return m_charquick[ch];
  526. }
  527. // letters are stored based on style and letter
  528. character_t ch = (style << 16) | letter;
  529. int low = 0;
  530. int high = m_numChars - 1;
  531. while (low <= high)
  532. {
  533. int mid = (low + high) >> 1;
  534. if (ch > m_char[mid].letterAndStyle)
  535. low = mid + 1;
  536. else if (ch < m_char[mid].letterAndStyle)
  537. high = mid - 1;
  538. else
  539. return &m_char[mid];
  540. }
  541. // if we get to here, then low is where we should insert the new character
  542. // increase the size of the buffer if we need it
  543. if (m_numChars >= m_maxChars)
  544. { // need to increase the size of the buffer
  545. Character *newTable = new Character[m_maxChars + CHAR_CHUNK];
  546. if (m_char)
  547. {
  548. memcpy(newTable, m_char, low * sizeof(Character));
  549. memcpy(newTable + low + 1, m_char + low, (m_numChars - low) * sizeof(Character));
  550. delete[] m_char;
  551. }
  552. m_char = newTable;
  553. m_maxChars += CHAR_CHUNK;
  554. }
  555. else
  556. { // just move the data along as necessary
  557. memmove(m_char + low + 1, m_char + low, (m_numChars - low) * sizeof(Character));
  558. }
  559. // render the character to our texture
  560. // must End() as we can't render text to our texture during a Begin(), End() block
  561. unsigned int nestedBeginCount = m_nestedBeginCount;
  562. m_nestedBeginCount = 1;
  563. if (nestedBeginCount) End();
  564. if (!CacheCharacter(letter, style, m_char + low))
  565. { // unable to cache character - try clearing them all out and starting over
  566. CLog::Log(LOGDEBUG, "%s: Unable to cache character. Clearing character cache of %i characters", __FUNCTION__, m_numChars);
  567. ClearCharacterCache();
  568. low = 0;
  569. if (!CacheCharacter(letter, style, m_char + low))
  570. {
  571. CLog::Log(LOGERROR, "%s: Unable to cache character (out of memory?)", __FUNCTION__);
  572. if (nestedBeginCount) Begin();
  573. m_nestedBeginCount = nestedBeginCount;
  574. return NULL;
  575. }
  576. }
  577. if (nestedBeginCount) Begin();
  578. m_nestedBeginCount = nestedBeginCount;
  579. // fixup quick access
  580. memset(m_charquick, 0, sizeof(m_charquick));
  581. for(int i=0;i<m_numChars;i++)
  582. {
  583. if ((m_char[i].letterAndStyle & 0xffff) < 255)
  584. {
  585. character_t ch = ((m_char[i].letterAndStyle & 0xffff0000) >> 8) | (m_char[i].letterAndStyle & 0xff);
  586. m_charquick[ch] = m_char+i;
  587. }
  588. }
  589. return m_char + low;
  590. }
  591. bool CGUIFontTTFBase::CacheCharacter(wchar_t letter, uint32_t style, Character *ch)
  592. {
  593. int glyph_index = FT_Get_Char_Index( m_face, letter );
  594. FT_Glyph glyph = NULL;
  595. if (FT_Load_Glyph( m_face, glyph_index, FT_LOAD_TARGET_LIGHT ))
  596. {
  597. CLog::Log(LOGDEBUG, "%s Failed to load glyph %x", __FUNCTION__, static_cast<uint32_t>(letter));
  598. return false;
  599. }
  600. // make bold if applicable
  601. if (style & FONT_STYLE_BOLD)
  602. SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_BOLD);
  603. // and italics if applicable
  604. if (style & FONT_STYLE_ITALICS)
  605. ObliqueGlyph(m_face->glyph);
  606. // and light if applicable
  607. if (style & FONT_STYLE_LIGHT)
  608. SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_LIGHT);
  609. // grab the glyph
  610. if (FT_Get_Glyph(m_face->glyph, &glyph))
  611. {
  612. CLog::Log(LOGDEBUG, "%s Failed to get glyph %x", __FUNCTION__, static_cast<uint32_t>(letter));
  613. return false;
  614. }
  615. if (m_stroker)
  616. FT_Glyph_StrokeBorder(&glyph, m_stroker, 0, 1);
  617. // render the glyph
  618. if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1))
  619. {
  620. CLog::Log(LOGDEBUG, "%s Failed to render glyph %x to a bitmap", __FUNCTION__, static_cast<uint32_t>(letter));
  621. return false;
  622. }
  623. FT_BitmapGlyph bitGlyph = (FT_BitmapGlyph)glyph;
  624. FT_Bitmap bitmap = bitGlyph->bitmap;
  625. bool isEmptyGlyph = (bitmap.width == 0 || bitmap.rows == 0);
  626. if (!isEmptyGlyph)
  627. {
  628. if (bitGlyph->left < 0)
  629. m_posX += -bitGlyph->left;
  630. // check we have enough room for the character.
  631. // cast-fest is here to avoid warnings due to freeetype version differences (signedness of width).
  632. if (static_cast<int>(m_posX + bitGlyph->left + bitmap.width) > static_cast<int>(m_textureWidth))
  633. { // no space - gotta drop to the next line (which means creating a new texture and copying it across)
  634. m_posX = 0;
  635. m_posY += GetTextureLineHeight();
  636. if (bitGlyph->left < 0)
  637. m_posX += -bitGlyph->left;
  638. if(m_posY + GetTextureLineHeight() >= m_textureHeight)
  639. {
  640. // create the new larger texture
  641. unsigned int newHeight = m_posY + GetTextureLineHeight();
  642. // check for max height
  643. if (newHeight > m_renderSystem->GetMaxTextureSize())
  644. {
  645. CLog::Log(LOGDEBUG, "%s: New cache texture is too large (%u > %u pixels long)", __FUNCTION__, newHeight, m_renderSystem->GetMaxTextureSize());
  646. FT_Done_Glyph(glyph);
  647. return false;
  648. }
  649. CBaseTexture* newTexture = NULL;
  650. newTexture = ReallocTexture(newHeight);
  651. if(newTexture == NULL)
  652. {
  653. FT_Done_Glyph(glyph);
  654. CLog::Log(LOGDEBUG, "%s: Failed to allocate new texture of height %u", __FUNCTION__, newHeight);
  655. return false;
  656. }
  657. m_texture = newTexture;
  658. }
  659. }
  660. if(m_texture == NULL)
  661. {
  662. FT_Done_Glyph(glyph);
  663. CLog::Log(LOGDEBUG, "%s: no texture to cache character to", __FUNCTION__);
  664. return false;
  665. }
  666. }
  667. // set the character in our table
  668. ch->letterAndStyle = (style << 16) | letter;
  669. ch->offsetX = (short)bitGlyph->left;
  670. ch->offsetY = (short)m_cellBaseLine - bitGlyph->top;
  671. ch->left = isEmptyGlyph ? 0 : ((float)m_posX + ch->offsetX);
  672. ch->top = isEmptyGlyph ? 0 : ((float)m_posY + ch->offsetY);
  673. ch->right = ch->left + bitmap.width;
  674. ch->bottom = ch->top + bitmap.rows;
  675. ch->advance = (float)MathUtils::round_int( (float)m_face->glyph->advance.x / 64 );
  676. // we need only render if we actually have some pixels
  677. if (!isEmptyGlyph)
  678. {
  679. // ensure our rect will stay inside the texture (it *should* but we need to be certain)
  680. unsigned int x1 = std::max(m_posX + ch->offsetX, 0);
  681. unsigned int y1 = std::max(m_posY + ch->offsetY, 0);
  682. unsigned int x2 = std::min(x1 + bitmap.width, m_textureWidth);
  683. unsigned int y2 = std::min(y1 + bitmap.rows, m_textureHeight);
  684. CopyCharToTexture(bitGlyph, x1, y1, x2, y2);
  685. m_posX += spacing_between_characters_in_texture + (unsigned short)std::max(ch->right - ch->left + ch->offsetX, ch->advance);
  686. }
  687. m_numChars++;
  688. // free the glyph
  689. FT_Done_Glyph(glyph);
  690. return true;
  691. }
  692. void CGUIFontTTFBase::RenderCharacter(float posX, float posY, const Character *ch, UTILS::Color color, bool roundX, std::vector<SVertex> &vertices)
  693. {
  694. // actual image width isn't same as the character width as that is
  695. // just baseline width and height should include the descent
  696. const float width = ch->right - ch->left;
  697. const float height = ch->bottom - ch->top;
  698. // return early if nothing to render
  699. if (width == 0 || height == 0)
  700. return;
  701. // posX and posY are relative to our origin, and the textcell is offset
  702. // from our (posX, posY). Plus, these are unscaled quantities compared to the underlying GUI resolution
  703. CRect vertex((posX + ch->offsetX) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(),
  704. (posY + ch->offsetY) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleY(),
  705. (posX + ch->offsetX + width) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(),
  706. (posY + ch->offsetY + height) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleY());
  707. vertex += CPoint(m_originX, m_originY);
  708. CRect texture(ch->left, ch->top, ch->right, ch->bottom);
  709. if (!m_renderSystem->ScissorsCanEffectClipping())
  710. CServiceBroker::GetWinSystem()->GetGfxContext().ClipRect(vertex, texture);
  711. // transform our positions - note, no scaling due to GUI calibration/resolution occurs
  712. float x[4], y[4], z[4];
  713. x[0] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x1, vertex.y1);
  714. x[1] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x2, vertex.y1);
  715. x[2] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x2, vertex.y2);
  716. x[3] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x1, vertex.y2);
  717. if (roundX)
  718. {
  719. // We only round the "left" side of the character, and then use the direction of rounding to
  720. // move the "right" side of the character. This ensures that a constant width is kept when rendering
  721. // the same letter at the same size at different places of the screen, avoiding the problem
  722. // of the "left" side rounding one way while the "right" side rounds the other way, thus getting
  723. // altering the width of thin characters substantially. This only really works for positive
  724. // coordinates (due to the direction of truncation for negatives) but this is the only case that
  725. // really interests us anyway.
  726. float rx0 = (float)MathUtils::round_int(x[0]);
  727. float rx3 = (float)MathUtils::round_int(x[3]);
  728. x[1] = (float)MathUtils::truncate_int(x[1]);
  729. x[2] = (float)MathUtils::truncate_int(x[2]);
  730. if (x[0] > 0.0f && rx0 > x[0])
  731. x[1] += 1;
  732. else if (x[0] < 0.0f && rx0 < x[0])
  733. x[1] -= 1;
  734. if (x[3] > 0.0f && rx3 > x[3])
  735. x[2] += 1;
  736. else if (x[3] < 0.0f && rx3 < x[3])
  737. x[2] -= 1;
  738. x[0] = rx0;
  739. x[3] = rx3;
  740. }
  741. y[0] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x1, vertex.y1));
  742. y[1] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x2, vertex.y1));
  743. y[2] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x2, vertex.y2));
  744. y[3] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x1, vertex.y2));
  745. z[0] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x1, vertex.y1));
  746. z[1] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x2, vertex.y1));
  747. z[2] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x2, vertex.y2));
  748. z[3] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x1, vertex.y2));
  749. // tex coords converted to 0..1 range
  750. float tl = texture.x1 * m_textureScaleX;
  751. float tr = texture.x2 * m_textureScaleX;
  752. float tt = texture.y1 * m_textureScaleY;
  753. float tb = texture.y2 * m_textureScaleY;
  754. vertices.resize(vertices.size() + 4);
  755. SVertex* v = &vertices[vertices.size() - 4];
  756. m_color = color;
  757. #if !defined(HAS_DX)
  758. unsigned char r = GET_R(color)
  759. , g = GET_G(color)
  760. , b = GET_B(color)
  761. , a = GET_A(color);
  762. #endif
  763. for(int i = 0; i < 4; i++)
  764. {
  765. #ifdef HAS_DX
  766. CD3DHelper::XMStoreColor(&v[i].col, color);
  767. #else
  768. v[i].r = r;
  769. v[i].g = g;
  770. v[i].b = b;
  771. v[i].a = a;
  772. #endif
  773. }
  774. #if defined(HAS_DX)
  775. for(int i = 0; i < 4; i++)
  776. {
  777. v[i].x = x[i];
  778. v[i].y = y[i];
  779. v[i].z = z[i];
  780. }
  781. v[0].u = tl;
  782. v[0].v = tt;
  783. v[1].u = tr;
  784. v[1].v = tt;
  785. v[2].u = tr;
  786. v[2].v = tb;
  787. v[3].u = tl;
  788. v[3].v = tb;
  789. #else
  790. // GL / GLES uses triangle strips, not quads, so have to rearrange the vertex order
  791. v[0].u = tl;
  792. v[0].v = tt;
  793. v[0].x = x[0];
  794. v[0].y = y[0];
  795. v[0].z = z[0];
  796. v[1].u = tl;
  797. v[1].v = tb;
  798. v[1].x = x[3];
  799. v[1].y = y[3];
  800. v[1].z = z[3];
  801. v[2].u = tr;
  802. v[2].v = tt;
  803. v[2].x = x[1];
  804. v[2].y = y[1];
  805. v[2].z = z[1];
  806. v[3].u = tr;
  807. v[3].v = tb;
  808. v[3].x = x[2];
  809. v[3].y = y[2];
  810. v[3].z = z[2];
  811. #endif
  812. }
  813. // Oblique code - original taken from freetype2 (ftsynth.c)
  814. void CGUIFontTTFBase::ObliqueGlyph(FT_GlyphSlot slot)
  815. {
  816. /* only oblique outline glyphs */
  817. if ( slot->format != FT_GLYPH_FORMAT_OUTLINE )
  818. return;
  819. /* we don't touch the advance width */
  820. /* For italic, simply apply a shear transform, with an angle */
  821. /* of about 12 degrees. */
  822. FT_Matrix transform;
  823. transform.xx = 0x10000L;
  824. transform.yx = 0x00000L;
  825. transform.xy = 0x06000L;
  826. transform.yy = 0x10000L;
  827. FT_Outline_Transform( &slot->outline, &transform );
  828. }
  829. // Embolden code - original taken from freetype2 (ftsynth.c)
  830. void CGUIFontTTFBase::SetGlyphStrength(FT_GlyphSlot slot, int glyphStrength)
  831. {
  832. if ( slot->format != FT_GLYPH_FORMAT_OUTLINE )
  833. return;
  834. /* some reasonable strength */
  835. FT_Pos strength = FT_MulFix( m_face->units_per_EM,
  836. m_face->size->metrics.y_scale ) / glyphStrength;
  837. FT_BBox bbox_before, bbox_after;
  838. FT_Outline_Get_CBox( &slot->outline, &bbox_before );
  839. FT_Outline_Embolden( &slot->outline, strength ); // ignore error
  840. FT_Outline_Get_CBox( &slot->outline, &bbox_after );
  841. FT_Pos dx = bbox_after.xMax - bbox_before.xMax;
  842. FT_Pos dy = bbox_after.yMax - bbox_before.yMax;
  843. if ( slot->advance.x )
  844. slot->advance.x += dx;
  845. if ( slot->advance.y )
  846. slot->advance.y += dy;
  847. slot->metrics.width += dx;
  848. slot->metrics.height += dy;
  849. slot->metrics.horiBearingY += dy;
  850. slot->metrics.horiAdvance += dx;
  851. slot->metrics.vertBearingX -= dx / 2;
  852. slot->metrics.vertBearingY += dy;
  853. slot->metrics.vertAdvance += dy;
  854. }