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.

635 lines
20KB

  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. AttributedString::Attribute::Attribute (const Range<int>& range_, const Colour& colour_)
  21. : range (range_), colour (new Colour (colour_))
  22. {
  23. }
  24. AttributedString::Attribute::Attribute (const Range<int>& range_, const Font& font_)
  25. : range (range_), font (new Font (font_))
  26. {
  27. }
  28. AttributedString::Attribute::Attribute (const Attribute& other)
  29. : range (other.range),
  30. font (other.font.createCopy()),
  31. colour (other.colour.createCopy())
  32. {
  33. }
  34. AttributedString::Attribute::~Attribute() {}
  35. //==============================================================================
  36. AttributedString::AttributedString()
  37. : lineSpacing (0.0f),
  38. justification (Justification::left),
  39. wordWrap (AttributedString::byWord),
  40. readingDirection (AttributedString::natural)
  41. {
  42. }
  43. AttributedString::AttributedString (const String& newString)
  44. : text (newString),
  45. lineSpacing (0.0f),
  46. justification (Justification::left),
  47. wordWrap (AttributedString::byWord),
  48. readingDirection (AttributedString::natural)
  49. {
  50. }
  51. AttributedString::AttributedString (const AttributedString& other)
  52. : text (other.text),
  53. lineSpacing (other.lineSpacing),
  54. justification (other.justification),
  55. wordWrap (other.wordWrap),
  56. readingDirection (other.readingDirection)
  57. {
  58. attributes.addCopiesOf (other.attributes);
  59. }
  60. AttributedString& AttributedString::operator= (const AttributedString& other)
  61. {
  62. if (this != &other)
  63. {
  64. text = other.text;
  65. lineSpacing = other.lineSpacing;
  66. justification = other.justification;
  67. wordWrap = other.wordWrap;
  68. readingDirection = other.readingDirection;
  69. attributes.clear();
  70. attributes.addCopiesOf (other.attributes);
  71. }
  72. return *this;
  73. }
  74. AttributedString::~AttributedString() {}
  75. void AttributedString::setText (const String& other)
  76. {
  77. text = other;
  78. }
  79. void AttributedString::setJustification (const Justification& newJustification) noexcept
  80. {
  81. justification = newJustification;
  82. }
  83. void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept
  84. {
  85. wordWrap = newWordWrap;
  86. }
  87. void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept
  88. {
  89. readingDirection = newReadingDirection;
  90. }
  91. void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
  92. {
  93. lineSpacing = newLineSpacing;
  94. }
  95. void AttributedString::setColour (const Range<int>& range, const Colour& colour)
  96. {
  97. attributes.add (new Attribute (range, colour));
  98. }
  99. void AttributedString::setFont (const Range<int>& range, const Font& font)
  100. {
  101. attributes.add (new Attribute (range, font));
  102. }
  103. void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
  104. {
  105. if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
  106. {
  107. if (! g.getInternalContext()->drawTextLayout (*this, area))
  108. {
  109. GlyphLayout layout;
  110. layout.setText (*this, area.getWidth());
  111. layout.draw (g, area);
  112. }
  113. }
  114. }
  115. //==============================================================================
  116. GlyphLayout::Glyph::Glyph (const int glyphCode_, const Point<float>& anchor_) noexcept
  117. : glyphCode (glyphCode_), anchor (anchor_)
  118. {
  119. }
  120. GlyphLayout::Glyph::~Glyph() {}
  121. //==============================================================================
  122. GlyphLayout::Run::Run()
  123. : colour (0xff000000)
  124. {
  125. }
  126. GlyphLayout::Run::Run (const Range<int>& range, const int numGlyphsToPreallocate)
  127. : stringRange (range), colour (0xff000000)
  128. {
  129. glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
  130. }
  131. GlyphLayout::Run::~Run() {}
  132. GlyphLayout::Glyph& GlyphLayout::Run::getGlyph (const int index) const
  133. {
  134. return *glyphs.getUnchecked (index);
  135. }
  136. void GlyphLayout::Run::ensureStorageAllocated (int numGlyphsNeeded)
  137. {
  138. glyphs.ensureStorageAllocated (numGlyphsNeeded);
  139. }
  140. void GlyphLayout::Run::setStringRange (const Range<int>& newStringRange) noexcept
  141. {
  142. stringRange = newStringRange;
  143. }
  144. void GlyphLayout::Run::setFont (const Font& newFont)
  145. {
  146. font = newFont;
  147. }
  148. void GlyphLayout::Run::setColour (const Colour& newColour) noexcept
  149. {
  150. colour = newColour;
  151. }
  152. void GlyphLayout::Run::addGlyph (Glyph* glyph)
  153. {
  154. glyphs.add (glyph);
  155. }
  156. //==============================================================================
  157. GlyphLayout::Line::Line() noexcept
  158. : ascent (0.0f), descent (0.0f), leading (0.0f)
  159. {
  160. }
  161. GlyphLayout::Line::Line (const Range<int>& stringRange_, const Point<float>& lineOrigin_,
  162. const float ascent_, const float descent_, const float leading_,
  163. const int numRunsToPreallocate)
  164. : stringRange (stringRange_), lineOrigin (lineOrigin_),
  165. ascent (ascent_), descent (descent_), leading (leading_)
  166. {
  167. runs.ensureStorageAllocated (numRunsToPreallocate);
  168. }
  169. GlyphLayout::Line::~Line()
  170. {
  171. }
  172. GlyphLayout::Run& GlyphLayout::Line::getRun (const int index) const noexcept
  173. {
  174. return *runs.getUnchecked (index);
  175. }
  176. void GlyphLayout::Line::setStringRange (const Range<int>& newStringRange) noexcept
  177. {
  178. stringRange = newStringRange;
  179. }
  180. void GlyphLayout::Line::setLineOrigin (const Point<float>& newLineOrigin) noexcept
  181. {
  182. lineOrigin = newLineOrigin;
  183. }
  184. void GlyphLayout::Line::setLeading (float newLeading) noexcept
  185. {
  186. leading = newLeading;
  187. }
  188. void GlyphLayout::Line::increaseAscentDescent (float newAscent, float newDescent) noexcept
  189. {
  190. ascent = jmax (ascent, newAscent);
  191. descent = jmax (descent, newDescent);
  192. }
  193. void GlyphLayout::Line::addRun (Run* run)
  194. {
  195. runs.add (run);
  196. }
  197. //==============================================================================
  198. GlyphLayout::GlyphLayout()
  199. : width (0), justification (Justification::topLeft)
  200. {
  201. }
  202. GlyphLayout::~GlyphLayout()
  203. {
  204. }
  205. void GlyphLayout::setText (const AttributedString& text, float maxWidth)
  206. {
  207. lines.clear();
  208. width = maxWidth;
  209. justification = text.getJustification();
  210. if (! createNativeLayout (text))
  211. createStandardLayout (text);
  212. }
  213. float GlyphLayout::getHeight() const noexcept
  214. {
  215. const Line* const lastLine = lines.getLast();
  216. return lastLine != nullptr ? lastLine->getLineOrigin().getY() + lastLine->getDescent()
  217. : 0;
  218. }
  219. GlyphLayout::Line& GlyphLayout::getLine (const int index) const
  220. {
  221. return *lines[index];
  222. }
  223. void GlyphLayout::ensureStorageAllocated (int numLinesNeeded)
  224. {
  225. lines.ensureStorageAllocated (numLinesNeeded);
  226. }
  227. void GlyphLayout::addLine (Line* line)
  228. {
  229. lines.add (line);
  230. }
  231. void GlyphLayout::draw (Graphics& g, const Rectangle<float>& area) const
  232. {
  233. const Point<float> origin (justification.appliedToRectangle (Rectangle<float> (0, 0, width, getHeight()), area).getPosition());
  234. LowLevelGraphicsContext& context = *g.getInternalContext();
  235. for (int i = 0; i < getNumLines(); ++i)
  236. {
  237. const Line& line = getLine (i);
  238. const Point<float> lineOrigin (origin + line.getLineOrigin());
  239. for (int j = 0; j < line.getNumRuns(); ++j)
  240. {
  241. const Run& run = line.getRun (j);
  242. context.setFont (run.getFont());
  243. context.setFill (run.getColour());
  244. for (int k = 0; k < run.getNumGlyphs(); ++k)
  245. {
  246. const Glyph& glyph = run.getGlyph (k);
  247. context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
  248. lineOrigin.y + glyph.anchor.y));
  249. }
  250. }
  251. }
  252. }
  253. //==============================================================================
  254. namespace GlyphLayoutHelpers
  255. {
  256. struct FontAndColour
  257. {
  258. FontAndColour (const Font* font_) noexcept : font (font_), colour (0xff000000) {}
  259. const Font* font;
  260. Colour colour;
  261. bool operator!= (const FontAndColour& other) const noexcept
  262. {
  263. return (font != other.font && *font != *other.font) || colour != other.colour;
  264. }
  265. };
  266. struct RunAttribute
  267. {
  268. RunAttribute (const FontAndColour& fontAndColour_, const Range<int>& range_) noexcept
  269. : fontAndColour (fontAndColour_), range (range_)
  270. {}
  271. FontAndColour fontAndColour;
  272. Range<int> range;
  273. };
  274. struct Token
  275. {
  276. Token (const String& t, const Font& f, const Colour& c, const bool isWhitespace_)
  277. : text (t), font (f), colour (c),
  278. area (font.getStringWidth (t), roundToInt (f.getHeight())),
  279. isWhitespace (isWhitespace_),
  280. isNewLine (t.containsChar ('\n') || t.containsChar ('\r'))
  281. {}
  282. const String text;
  283. const Font font;
  284. const Colour colour;
  285. Rectangle<int> area;
  286. int line, lineHeight;
  287. const bool isWhitespace, isNewLine;
  288. private:
  289. Token& operator= (const Token&);
  290. };
  291. class TokenList
  292. {
  293. public:
  294. TokenList() noexcept : totalLines (0) {}
  295. void createLayout (const AttributedString& text, GlyphLayout& glyphLayout)
  296. {
  297. tokens.ensureStorageAllocated (64);
  298. glyphLayout.ensureStorageAllocated (totalLines);
  299. addTextRuns (text);
  300. layout ((int) glyphLayout.getWidth());
  301. int charPosition = 0;
  302. int lineStartPosition = 0;
  303. int runStartPosition = 0;
  304. GlyphLayout::Line* glyphLine = new GlyphLayout::Line();
  305. GlyphLayout::Run* glyphRun = new GlyphLayout::Run();
  306. for (int i = 0; i < tokens.size(); ++i)
  307. {
  308. const Token* const t = tokens.getUnchecked (i);
  309. const Point<float> tokenPos (t->area.getPosition().toFloat());
  310. Array <int> newGlyphs;
  311. Array <float> xOffsets;
  312. t->font.getGlyphPositions (t->text.trimEnd(), newGlyphs, xOffsets);
  313. glyphRun->ensureStorageAllocated (glyphRun->getNumGlyphs() + newGlyphs.size());
  314. for (int j = 0; j < newGlyphs.size(); ++j)
  315. {
  316. if (charPosition == lineStartPosition)
  317. glyphLine->setLineOrigin (tokenPos.translated (0, t->font.getAscent()));
  318. glyphRun->addGlyph (new GlyphLayout::Glyph (newGlyphs.getUnchecked(j),
  319. Point<float> (tokenPos.getX() + xOffsets.getUnchecked (j), 0)));
  320. ++charPosition;
  321. }
  322. if (t->isWhitespace || t->isNewLine)
  323. ++charPosition;
  324. const Token* const nextToken = tokens [i + 1];
  325. if (nextToken == nullptr) // this is the last token
  326. {
  327. addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
  328. glyphLine->setStringRange (Range<int> (lineStartPosition, charPosition));
  329. glyphLayout.addLine (glyphLine);
  330. }
  331. else
  332. {
  333. if (t->font != nextToken->font || t->colour != nextToken->colour)
  334. {
  335. addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
  336. runStartPosition = charPosition;
  337. glyphRun = new GlyphLayout::Run();
  338. }
  339. if (t->line != nextToken->line)
  340. {
  341. addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
  342. glyphLine->setStringRange (Range<int> (lineStartPosition, charPosition));
  343. glyphLayout.addLine (glyphLine);
  344. runStartPosition = charPosition;
  345. lineStartPosition = charPosition;
  346. glyphLine = new GlyphLayout::Line();
  347. glyphRun = new GlyphLayout::Run();
  348. }
  349. }
  350. }
  351. if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
  352. {
  353. const int totalW = (int) glyphLayout.getWidth();
  354. for (int i = 0; i < totalLines; ++i)
  355. {
  356. const int lineW = getLineWidth (i);
  357. float dx = 0;
  358. if ((text.getJustification().getFlags() & Justification::right) != 0)
  359. dx = (float) (totalW - lineW);
  360. else
  361. dx = (totalW - lineW) / 2.0f;
  362. GlyphLayout::Line& glyphLine = glyphLayout.getLine (i);
  363. glyphLine.setLineOrigin (glyphLine.getLineOrigin().translated (dx, 0));
  364. }
  365. }
  366. }
  367. private:
  368. static void addRun (GlyphLayout::Line* glyphLine, GlyphLayout::Run* glyphRun,
  369. const Token* const t, const int start, const int end)
  370. {
  371. glyphRun->setStringRange (Range<int> (start, end));
  372. glyphRun->setFont (t->font);
  373. glyphRun->setColour (t->colour);
  374. glyphLine->increaseAscentDescent (t->font.getAscent(), t->font.getDescent());
  375. glyphLine->addRun (glyphRun);
  376. }
  377. void appendText (const AttributedString& text, const Range<int>& stringRange,
  378. const Font& font, const Colour& colour)
  379. {
  380. String stringText (text.getText().substring(stringRange.getStart(), stringRange.getEnd()));
  381. String::CharPointerType t (stringText.getCharPointer());
  382. String currentString;
  383. int lastCharType = 0;
  384. for (;;)
  385. {
  386. const juce_wchar c = t.getAndAdvance();
  387. if (c == 0)
  388. break;
  389. int charType;
  390. if (c == '\r' || c == '\n')
  391. charType = 0;
  392. else if (CharacterFunctions::isWhitespace (c))
  393. charType = 2;
  394. else
  395. charType = 1;
  396. if (charType == 0 || charType != lastCharType)
  397. {
  398. if (currentString.isNotEmpty())
  399. tokens.add (new Token (currentString, font, colour,
  400. lastCharType == 2 || lastCharType == 0));
  401. currentString = String::charToString (c);
  402. if (c == '\r' && *t == '\n')
  403. currentString += t.getAndAdvance();
  404. }
  405. else
  406. {
  407. currentString += c;
  408. }
  409. lastCharType = charType;
  410. }
  411. if (currentString.isNotEmpty())
  412. tokens.add (new Token (currentString, font, colour, lastCharType == 2));
  413. }
  414. void layout (const int maxWidth)
  415. {
  416. int x = 0, y = 0, h = 0;
  417. int i;
  418. for (i = 0; i < tokens.size(); ++i)
  419. {
  420. Token* const t = tokens.getUnchecked(i);
  421. t->area.setPosition (x, y);
  422. t->line = totalLines;
  423. x += t->area.getWidth();
  424. h = jmax (h, t->area.getHeight());
  425. const Token* nextTok = tokens[i + 1];
  426. if (nextTok == 0)
  427. break;
  428. if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->area.getWidth() > maxWidth))
  429. {
  430. setLastLineHeight (i + 1, h);
  431. x = 0;
  432. y += h;
  433. h = 0;
  434. ++totalLines;
  435. }
  436. }
  437. setLastLineHeight (jmin (i + 1, tokens.size()), h);
  438. ++totalLines;
  439. }
  440. void setLastLineHeight (int i, const int height) noexcept
  441. {
  442. while (--i >= 0)
  443. {
  444. Token* const tok = tokens.getUnchecked (i);
  445. if (tok->line == totalLines)
  446. tok->lineHeight = height;
  447. else
  448. break;
  449. }
  450. }
  451. int getLineWidth (const int lineNumber) const noexcept
  452. {
  453. int maxW = 0;
  454. for (int i = tokens.size(); --i >= 0;)
  455. {
  456. const Token* const t = tokens.getUnchecked (i);
  457. if (t->line == lineNumber && ! t->isWhitespace)
  458. maxW = jmax (maxW, t->area.getRight());
  459. }
  460. return maxW;
  461. }
  462. void addTextRuns (const AttributedString& text)
  463. {
  464. Font defaultFont;
  465. Array<RunAttribute> runAttributes;
  466. {
  467. const int stringLength = text.getText().length();
  468. int rangeStart = 0;
  469. FontAndColour lastFontAndColour (nullptr);
  470. // Iterate through every character in the string
  471. for (int i = 0; i < stringLength; ++i)
  472. {
  473. FontAndColour newFontAndColour (&defaultFont);
  474. const int numCharacterAttributes = text.getNumAttributes();
  475. for (int j = 0; j < numCharacterAttributes; ++j)
  476. {
  477. const AttributedString::Attribute* const attr = text.getAttribute (j);
  478. // Check if the current character falls within the range of a font attribute
  479. if (attr->getFont() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
  480. newFontAndColour.font = attr->getFont();
  481. // Check if the current character falls within the range of a foreground colour attribute
  482. if (attr->getColour() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
  483. newFontAndColour.colour = *attr->getColour();
  484. }
  485. if (i > 0 && (newFontAndColour != lastFontAndColour || i == stringLength - 1))
  486. {
  487. runAttributes.add (RunAttribute (lastFontAndColour,
  488. Range<int> (rangeStart, (i < stringLength - 1) ? i : (i + 1))));
  489. rangeStart = i;
  490. }
  491. lastFontAndColour = newFontAndColour;
  492. }
  493. }
  494. for (int i = 0; i < runAttributes.size(); ++i)
  495. {
  496. const RunAttribute& r = runAttributes.getReference(i);
  497. appendText (text, r.range, *(r.fontAndColour.font), r.fontAndColour.colour);
  498. }
  499. }
  500. OwnedArray<Token> tokens;
  501. int totalLines;
  502. JUCE_DECLARE_NON_COPYABLE (TokenList);
  503. };
  504. }
  505. //==============================================================================
  506. void GlyphLayout::createStandardLayout (const AttributedString& text)
  507. {
  508. GlyphLayoutHelpers::TokenList l;
  509. l.createLayout (text, *this);
  510. }
  511. END_JUCE_NAMESPACE