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.

376 lines
9.7KB

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