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.

382 lines
9.9KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 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_GraphicsContext.h"
  22. //==============================================================================
  23. class TextLayout::Token
  24. {
  25. public:
  26. Token (const String& t,
  27. const Font& f,
  28. const bool isWhitespace_)
  29. : text (t),
  30. font (f),
  31. x(0),
  32. y(0),
  33. isWhitespace (isWhitespace_)
  34. {
  35. w = font.getStringWidth (t);
  36. h = roundToInt (f.getHeight());
  37. isNewLine = t.containsChar ('\n') || t.containsChar ('\r');
  38. }
  39. Token (const Token& other)
  40. : text (other.text),
  41. font (other.font),
  42. x (other.x),
  43. y (other.y),
  44. w (other.w),
  45. h (other.h),
  46. line (other.line),
  47. lineHeight (other.lineHeight),
  48. isWhitespace (other.isWhitespace),
  49. isNewLine (other.isNewLine)
  50. {
  51. }
  52. void draw (Graphics& g,
  53. const int xOffset,
  54. const int yOffset)
  55. {
  56. if (! isWhitespace)
  57. {
  58. g.setFont (font);
  59. g.drawSingleLineText (text.trimEnd(),
  60. xOffset + x,
  61. yOffset + y + (lineHeight - h)
  62. + roundToInt (font.getAscent()));
  63. }
  64. }
  65. String text;
  66. Font font;
  67. int x, y, w, h;
  68. int line, lineHeight;
  69. bool isWhitespace, isNewLine;
  70. private:
  71. JUCE_LEAK_DETECTOR (Token);
  72. };
  73. //==============================================================================
  74. TextLayout::TextLayout()
  75. : totalLines (0)
  76. {
  77. tokens.ensureStorageAllocated (64);
  78. }
  79. TextLayout::TextLayout (const String& text, const Font& font)
  80. : totalLines (0)
  81. {
  82. tokens.ensureStorageAllocated (64);
  83. appendText (text, font);
  84. }
  85. TextLayout::TextLayout (const TextLayout& other)
  86. : totalLines (0)
  87. {
  88. *this = other;
  89. }
  90. TextLayout& TextLayout::operator= (const TextLayout& other)
  91. {
  92. if (this != &other)
  93. {
  94. clear();
  95. totalLines = other.totalLines;
  96. tokens.addCopiesOf (other.tokens);
  97. }
  98. return *this;
  99. }
  100. TextLayout::~TextLayout()
  101. {
  102. clear();
  103. }
  104. //==============================================================================
  105. void TextLayout::clear()
  106. {
  107. tokens.clear();
  108. totalLines = 0;
  109. }
  110. bool TextLayout::isEmpty() const
  111. {
  112. return tokens.size() == 0;
  113. }
  114. void TextLayout::appendText (const String& text, const Font& font)
  115. {
  116. String::CharPointerType t (text.getCharPointer());
  117. String currentString;
  118. int lastCharType = 0;
  119. for (;;)
  120. {
  121. const juce_wchar c = t.getAndAdvance();
  122. if (c == 0)
  123. break;
  124. int charType;
  125. if (c == '\r' || c == '\n')
  126. {
  127. charType = 0;
  128. }
  129. else if (CharacterFunctions::isWhitespace (c))
  130. {
  131. charType = 2;
  132. }
  133. else
  134. {
  135. charType = 1;
  136. }
  137. if (charType == 0 || charType != lastCharType)
  138. {
  139. if (currentString.isNotEmpty())
  140. {
  141. tokens.add (new Token (currentString, font,
  142. lastCharType == 2 || lastCharType == 0));
  143. }
  144. currentString = String::charToString (c);
  145. if (c == '\r' && *t == '\n')
  146. currentString += t.getAndAdvance();
  147. }
  148. else
  149. {
  150. currentString += c;
  151. }
  152. lastCharType = charType;
  153. }
  154. if (currentString.isNotEmpty())
  155. tokens.add (new Token (currentString, font, lastCharType == 2));
  156. }
  157. void TextLayout::setText (const String& text, const Font& font)
  158. {
  159. clear();
  160. appendText (text, font);
  161. }
  162. //==============================================================================
  163. void TextLayout::layout (int maxWidth,
  164. const Justification& justification,
  165. const bool attemptToBalanceLineLengths)
  166. {
  167. if (attemptToBalanceLineLengths)
  168. {
  169. const int originalW = maxWidth;
  170. int bestWidth = maxWidth;
  171. float bestLineProportion = 0.0f;
  172. while (maxWidth > originalW / 2)
  173. {
  174. layout (maxWidth, justification, false);
  175. if (getNumLines() <= 1)
  176. return;
  177. const int lastLineW = getLineWidth (getNumLines() - 1);
  178. const int lastButOneLineW = getLineWidth (getNumLines() - 2);
  179. const float prop = lastLineW / (float) lastButOneLineW;
  180. if (prop > 0.9f)
  181. return;
  182. if (prop > bestLineProportion)
  183. {
  184. bestLineProportion = prop;
  185. bestWidth = maxWidth;
  186. }
  187. maxWidth -= 10;
  188. }
  189. layout (bestWidth, justification, false);
  190. }
  191. else
  192. {
  193. int x = 0;
  194. int y = 0;
  195. int h = 0;
  196. totalLines = 0;
  197. int i;
  198. for (i = 0; i < tokens.size(); ++i)
  199. {
  200. Token* const t = tokens.getUnchecked(i);
  201. t->x = x;
  202. t->y = y;
  203. t->line = totalLines;
  204. x += t->w;
  205. h = jmax (h, t->h);
  206. const Token* nextTok = tokens [i + 1];
  207. if (nextTok == 0)
  208. break;
  209. if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->w > maxWidth))
  210. {
  211. // finished a line, so go back and update the heights of the things on it
  212. for (int j = i; j >= 0; --j)
  213. {
  214. Token* const tok = tokens.getUnchecked(j);
  215. if (tok->line == totalLines)
  216. tok->lineHeight = h;
  217. else
  218. break;
  219. }
  220. x = 0;
  221. y += h;
  222. h = 0;
  223. ++totalLines;
  224. }
  225. }
  226. // finished a line, so go back and update the heights of the things on it
  227. for (int j = jmin (i, tokens.size() - 1); j >= 0; --j)
  228. {
  229. Token* const t = tokens.getUnchecked(j);
  230. if (t->line == totalLines)
  231. t->lineHeight = h;
  232. else
  233. break;
  234. }
  235. ++totalLines;
  236. if (! justification.testFlags (Justification::left))
  237. {
  238. int totalW = getWidth();
  239. for (i = totalLines; --i >= 0;)
  240. {
  241. const int lineW = getLineWidth (i);
  242. int dx = 0;
  243. if (justification.testFlags (Justification::horizontallyCentred))
  244. dx = (totalW - lineW) / 2;
  245. else if (justification.testFlags (Justification::right))
  246. dx = totalW - lineW;
  247. for (int j = tokens.size(); --j >= 0;)
  248. {
  249. Token* const t = tokens.getUnchecked(j);
  250. if (t->line == i)
  251. t->x += dx;
  252. }
  253. }
  254. }
  255. }
  256. }
  257. //==============================================================================
  258. int TextLayout::getLineWidth (const int lineNumber) const
  259. {
  260. int maxW = 0;
  261. for (int i = tokens.size(); --i >= 0;)
  262. {
  263. const Token* const t = tokens.getUnchecked(i);
  264. if (t->line == lineNumber && ! t->isWhitespace)
  265. maxW = jmax (maxW, t->x + t->w);
  266. }
  267. return maxW;
  268. }
  269. int TextLayout::getWidth() const
  270. {
  271. int maxW = 0;
  272. for (int i = tokens.size(); --i >= 0;)
  273. {
  274. const Token* const t = tokens.getUnchecked(i);
  275. if (! t->isWhitespace)
  276. maxW = jmax (maxW, t->x + t->w);
  277. }
  278. return maxW;
  279. }
  280. int TextLayout::getHeight() const
  281. {
  282. int maxH = 0;
  283. for (int i = tokens.size(); --i >= 0;)
  284. {
  285. const Token* const t = tokens.getUnchecked(i);
  286. if (! t->isWhitespace)
  287. maxH = jmax (maxH, t->y + t->h);
  288. }
  289. return maxH;
  290. }
  291. //==============================================================================
  292. void TextLayout::draw (Graphics& g,
  293. const int xOffset,
  294. const int yOffset) const
  295. {
  296. for (int i = tokens.size(); --i >= 0;)
  297. tokens.getUnchecked(i)->draw (g, xOffset, yOffset);
  298. }
  299. void TextLayout::drawWithin (Graphics& g,
  300. int x, int y, int w, int h,
  301. const Justification& justification) const
  302. {
  303. justification.applyToRectangle (x, y, getWidth(), getHeight(),
  304. x, y, w, h);
  305. draw (g, x, y);
  306. }
  307. END_JUCE_NAMESPACE