The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

384 lines
10KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_TextLayout.h"
  21. #include "../contexts/juce_Graphics.h"
  22. //==============================================================================
  23. class TextLayout::Token
  24. {
  25. public:
  26. String text;
  27. Font font;
  28. int x, y, w, h;
  29. int line, lineHeight;
  30. bool isWhitespace, isNewLine;
  31. Token (const String& t,
  32. const Font& f,
  33. const bool isWhitespace_) throw()
  34. : text (t),
  35. font (f),
  36. x(0),
  37. y(0),
  38. isWhitespace (isWhitespace_)
  39. {
  40. w = font.getStringWidth (t);
  41. h = roundToInt (f.getHeight());
  42. isNewLine = t.containsAnyOf (T("\r\n"));
  43. }
  44. Token (const Token& other) throw()
  45. : text (other.text),
  46. font (other.font),
  47. x (other.x),
  48. y (other.y),
  49. w (other.w),
  50. h (other.h),
  51. line (other.line),
  52. lineHeight (other.lineHeight),
  53. isWhitespace (other.isWhitespace),
  54. isNewLine (other.isNewLine)
  55. {
  56. }
  57. ~Token() throw()
  58. {
  59. }
  60. void draw (Graphics& g,
  61. const int xOffset,
  62. const int yOffset) throw()
  63. {
  64. if (! isWhitespace)
  65. {
  66. g.setFont (font);
  67. g.drawSingleLineText (text.trimEnd(),
  68. xOffset + x,
  69. yOffset + y + (lineHeight - h)
  70. + roundToInt (font.getAscent()));
  71. }
  72. }
  73. juce_UseDebuggingNewOperator
  74. };
  75. //==============================================================================
  76. TextLayout::TextLayout() throw()
  77. : totalLines (0)
  78. {
  79. tokens.ensureStorageAllocated (64);
  80. }
  81. TextLayout::TextLayout (const String& text,
  82. const Font& font) throw()
  83. : totalLines (0)
  84. {
  85. tokens.ensureStorageAllocated (64);
  86. appendText (text, font);
  87. }
  88. TextLayout::TextLayout (const TextLayout& other) throw()
  89. : totalLines (0)
  90. {
  91. *this = other;
  92. }
  93. const TextLayout& TextLayout::operator= (const TextLayout& other) throw()
  94. {
  95. if (this != &other)
  96. {
  97. clear();
  98. totalLines = other.totalLines;
  99. for (int i = 0; i < other.tokens.size(); ++i)
  100. tokens.add (new Token (*other.tokens.getUnchecked(i)));
  101. }
  102. return *this;
  103. }
  104. TextLayout::~TextLayout() throw()
  105. {
  106. clear();
  107. }
  108. //==============================================================================
  109. void TextLayout::clear() throw()
  110. {
  111. tokens.clear();
  112. totalLines = 0;
  113. }
  114. void TextLayout::appendText (const String& text,
  115. const Font& font) throw()
  116. {
  117. const tchar* t = text;
  118. String currentString;
  119. int lastCharType = 0;
  120. for (;;)
  121. {
  122. const tchar c = *t++;
  123. if (c == 0)
  124. break;
  125. int charType;
  126. if (c == T('\r') || c == T('\n'))
  127. {
  128. charType = 0;
  129. }
  130. else if (CharacterFunctions::isWhitespace (c))
  131. {
  132. charType = 2;
  133. }
  134. else
  135. {
  136. charType = 1;
  137. }
  138. if (charType == 0 || charType != lastCharType)
  139. {
  140. if (currentString.isNotEmpty())
  141. {
  142. tokens.add (new Token (currentString, font,
  143. lastCharType == 2 || lastCharType == 0));
  144. }
  145. currentString = String::charToString (c);
  146. if (c == T('\r') && *t == T('\n'))
  147. currentString += *t++;
  148. }
  149. else
  150. {
  151. currentString += c;
  152. }
  153. lastCharType = charType;
  154. }
  155. if (currentString.isNotEmpty())
  156. tokens.add (new Token (currentString, font, lastCharType == 2));
  157. }
  158. void TextLayout::setText (const String& text, const Font& font) throw()
  159. {
  160. clear();
  161. appendText (text, font);
  162. }
  163. //==============================================================================
  164. void TextLayout::layout (int maxWidth,
  165. const Justification& justification,
  166. const bool attemptToBalanceLineLengths) throw()
  167. {
  168. if (attemptToBalanceLineLengths)
  169. {
  170. const int originalW = maxWidth;
  171. int bestWidth = maxWidth;
  172. float bestLineProportion = 0.0f;
  173. while (maxWidth > originalW / 2)
  174. {
  175. layout (maxWidth, justification, false);
  176. if (getNumLines() <= 1)
  177. return;
  178. const int lastLineW = getLineWidth (getNumLines() - 1);
  179. const int lastButOneLineW = getLineWidth (getNumLines() - 2);
  180. const float prop = lastLineW / (float) lastButOneLineW;
  181. if (prop > 0.9f)
  182. return;
  183. if (prop > bestLineProportion)
  184. {
  185. bestLineProportion = prop;
  186. bestWidth = maxWidth;
  187. }
  188. maxWidth -= 10;
  189. }
  190. layout (bestWidth, justification, false);
  191. }
  192. else
  193. {
  194. int x = 0;
  195. int y = 0;
  196. int h = 0;
  197. totalLines = 0;
  198. int i;
  199. for (i = 0; i < tokens.size(); ++i)
  200. {
  201. Token* const t = tokens.getUnchecked(i);
  202. t->x = x;
  203. t->y = y;
  204. t->line = totalLines;
  205. x += t->w;
  206. h = jmax (h, t->h);
  207. const Token* nextTok = tokens [i + 1];
  208. if (nextTok == 0)
  209. break;
  210. if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->w > maxWidth))
  211. {
  212. // finished a line, so go back and update the heights of the things on it
  213. for (int j = i; j >= 0; --j)
  214. {
  215. Token* const tok = tokens.getUnchecked(j);
  216. if (tok->line == totalLines)
  217. tok->lineHeight = h;
  218. else
  219. break;
  220. }
  221. x = 0;
  222. y += h;
  223. h = 0;
  224. ++totalLines;
  225. }
  226. }
  227. // finished a line, so go back and update the heights of the things on it
  228. for (int j = jmin (i, tokens.size() - 1); j >= 0; --j)
  229. {
  230. Token* const t = tokens.getUnchecked(j);
  231. if (t->line == totalLines)
  232. t->lineHeight = h;
  233. else
  234. break;
  235. }
  236. ++totalLines;
  237. if (! justification.testFlags (Justification::left))
  238. {
  239. int totalW = getWidth();
  240. for (i = totalLines; --i >= 0;)
  241. {
  242. const int lineW = getLineWidth (i);
  243. int dx = 0;
  244. if (justification.testFlags (Justification::horizontallyCentred))
  245. dx = (totalW - lineW) / 2;
  246. else if (justification.testFlags (Justification::right))
  247. dx = totalW - lineW;
  248. for (int j = tokens.size(); --j >= 0;)
  249. {
  250. Token* const t = tokens.getUnchecked(j);
  251. if (t->line == i)
  252. t->x += dx;
  253. }
  254. }
  255. }
  256. }
  257. }
  258. //==============================================================================
  259. int TextLayout::getLineWidth (const int lineNumber) const throw()
  260. {
  261. int maxW = 0;
  262. for (int i = tokens.size(); --i >= 0;)
  263. {
  264. const Token* const t = tokens.getUnchecked(i);
  265. if (t->line == lineNumber && ! t->isWhitespace)
  266. maxW = jmax (maxW, t->x + t->w);
  267. }
  268. return maxW;
  269. }
  270. int TextLayout::getWidth() const throw()
  271. {
  272. int maxW = 0;
  273. for (int i = tokens.size(); --i >= 0;)
  274. {
  275. const Token* const t = tokens.getUnchecked(i);
  276. if (! t->isWhitespace)
  277. maxW = jmax (maxW, t->x + t->w);
  278. }
  279. return maxW;
  280. }
  281. int TextLayout::getHeight() const throw()
  282. {
  283. int maxH = 0;
  284. for (int i = tokens.size(); --i >= 0;)
  285. {
  286. const Token* const t = tokens.getUnchecked(i);
  287. if (! t->isWhitespace)
  288. maxH = jmax (maxH, t->y + t->h);
  289. }
  290. return maxH;
  291. }
  292. //==============================================================================
  293. void TextLayout::draw (Graphics& g,
  294. const int xOffset,
  295. const int yOffset) const throw()
  296. {
  297. for (int i = tokens.size(); --i >= 0;)
  298. tokens.getUnchecked(i)->draw (g, xOffset, yOffset);
  299. }
  300. void TextLayout::drawWithin (Graphics& g,
  301. int x, int y, int w, int h,
  302. const Justification& justification) const throw()
  303. {
  304. justification.applyToRectangle (x, y, getWidth(), getHeight(),
  305. x, y, w, h);
  306. draw (g, x, y);
  307. }
  308. END_JUCE_NAMESPACE