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.

399 lines
11KB

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