Browse Source

Complete rewrite of the TextLayout class, to provide better support for native platform layout functions. It now works with the AttributedString class, to provide a pre-formatted AttributedString that can be drawn.

tags/2021-05-28
jules 14 years ago
parent
commit
58db7eb880
16 changed files with 952 additions and 1158 deletions
  1. +6
    -7
      extras/Introjucer/Source/Project/jucer_ProjectInformationComponent.cpp
  2. +6
    -8
      extras/Introjucer/Source/Utility/jucer_MiscUtilities.cpp
  3. +0
    -1
      extras/Introjucer/Source/Utility/jucer_MiscUtilities.h
  4. +41
    -49
      extras/the jucer/src/ui/jucer_PrefsPanel.cpp
  5. +69
    -481
      modules/juce_graphics/fonts/juce_AttributedString.cpp
  6. +33
    -144
      modules/juce_graphics/fonts/juce_AttributedString.h
  7. +471
    -233
      modules/juce_graphics/fonts/juce_TextLayout.cpp
  8. +105
    -84
      modules/juce_graphics/fonts/juce_TextLayout.h
  9. +1
    -1
      modules/juce_graphics/juce_graphics.h
  10. +143
    -84
      modules/juce_graphics/native/juce_mac_Fonts.mm
  11. +27
    -24
      modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp
  12. +18
    -18
      modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.cpp
  13. +16
    -11
      modules/juce_gui_basics/windows/juce_AlertWindow.cpp
  14. +1
    -0
      modules/juce_gui_basics/windows/juce_AlertWindow.h
  15. +14
    -13
      modules/juce_gui_extra/misc/juce_BubbleMessageComponent.cpp
  16. +1
    -0
      modules/juce_gui_extra/misc/juce_BubbleMessageComponent.h

+ 6
- 7
extras/Introjucer/Source/Project/jucer_ProjectInformationComponent.cpp View File

@@ -730,16 +730,16 @@ public:
void paint (Graphics& g) void paint (Graphics& g)
{ {
g.setColour (Colour::greyLevel (0.15f));
g.setFont (13.0f);
AttributedString s;
s.setJustification (Justification::centredLeft);
s.append (lastTip, Font (14.0f), Colour::greyLevel (0.15f));
TextLayout tl; TextLayout tl;
tl.appendText (lastTip, Font (14.0f));
tl.layout (getWidth() - 10, Justification::left, true); // try to make it look nice
tl.createLayoutWithBalancedLineLengths (s, getWidth() - 10.0f);
if (tl.getNumLines() > 3) if (tl.getNumLines() > 3)
tl.layout (getWidth() - 10, Justification::left, false); // too big, so just squash it in..
tl.createLayout (s, getWidth() - 10.0f);
tl.drawWithin (g, 0, 0, getWidth(), getHeight(), Justification::centred);
tl.draw (g, getLocalBounds().toFloat());
} }
void timerCallback() void timerCallback()
@@ -785,7 +785,6 @@ private:
return String::empty; return String::empty;
} }
TextLayout layout;
Component* lastComp; Component* lastComp;
String lastTip; String lastTip;
}; };


+ 6
- 8
extras/Introjucer/Source/Utility/jucer_MiscUtilities.cpp View File

@@ -231,18 +231,16 @@ PropertyPanelWithTooltips::~PropertyPanelWithTooltips()
void PropertyPanelWithTooltips::paint (Graphics& g) void PropertyPanelWithTooltips::paint (Graphics& g)
{ {
g.setColour (Colour::greyLevel (0.15f));
g.setFont (13.0f);
AttributedString s;
s.setJustification (Justification::centredLeft);
s.append (lastTip, Font (14.0f), Colour::greyLevel (0.15f));
TextLayout tl; TextLayout tl;
tl.appendText (lastTip, Font (14.0f));
tl.layout (getWidth() - 10, Justification::left, true); // try to make it look nice
tl.createLayoutWithBalancedLineLengths (s, getWidth() - 10.0f);
if (tl.getNumLines() > 3) if (tl.getNumLines() > 3)
tl.layout (getWidth() - 10, Justification::left, false); // too big, so just squash it in..
tl.createLayout (s, getWidth() - 10.0f);
Rectangle<int> r (getTipArea());
tl.drawWithin (g, r.getX(), r.getY(), r.getWidth(), r.getHeight(),
Justification::bottomLeft);
tl.draw (g, getLocalBounds().toFloat());
} }
void PropertyPanelWithTooltips::resized() void PropertyPanelWithTooltips::resized()


+ 0
- 1
extras/Introjucer/Source/Utility/jucer_MiscUtilities.h View File

@@ -62,7 +62,6 @@ public:
private: private:
PropertyPanel panel; PropertyPanel panel;
TextLayout layout;
Component* lastComp; Component* lastComp;
String lastTip; String lastTip;


+ 41
- 49
extras/the jucer/src/ui/jucer_PrefsPanel.cpp View File

@@ -29,88 +29,80 @@
//============================================================================== //==============================================================================
class MiscPage : public Component class MiscPage : public Component
{ {
FilenameComponent* templateDir;
public: public:
MiscPage() MiscPage()
: templateDir ("C++ template folder:",
StoredSettings::getInstance()->getTemplatesDir(),
true, true, false,
"*.*", String::empty,
"(select the directory containing template .cpp and .h files)"),
label (String::empty, templateDir.getName())
{ {
addAndMakeVisible (templateDir
= new FilenameComponent ("C++ template folder:",
StoredSettings::getInstance()->getTemplatesDir(),
true,
true,
false,
"*.*",
String::empty,
"(select the directory containing template .cpp and .h files)"));
(new Label (String::empty, templateDir->getName()))->attachToComponent (templateDir, true);
addAndMakeVisible (&templateDir);
label.attachToComponent (&templateDir, true);
} }
~MiscPage() ~MiscPage()
{ {
StoredSettings::getInstance()->setTemplatesDir (templateDir->getCurrentFile());
deleteAllChildren();
StoredSettings::getInstance()->setTemplatesDir (templateDir.getCurrentFile());
} }
void resized() void resized()
{ {
templateDir->setBounds (150, 16, getWidth() - 160, 22);
templateDir.setBounds (150, 16, getWidth() - 160, 22);
} }
private:
FilenameComponent templateDir;
Label label;
}; };
//============================================================================== //==============================================================================
class AboutPage : public Component class AboutPage : public Component
{ {
HyperlinkButton* link;
Image logo;
TextLayout text1, text2;
public: public:
AboutPage() AboutPage()
: link ("www.rawmaterialsoftware.com/juce",
URL ("http://www.rawmaterialsoftware.com/juce")),
logo (ImageCache::getFromMemory (BinaryData::jules_jpg, BinaryData::jules_jpgSize))
{ {
logo = ImageCache::getFromMemory (BinaryData::jules_jpg, BinaryData::jules_jpgSize);
text1.appendText ("Programmer Julian Storer, seen here demonstrating a beard designed to "
"gain approval from the Linux programming community. Each hair of the beard "
"represents one line of source code from the ", Font (13.0f));
text1.appendText ("Jucer", Font (13.0f, Font::bold));
text1.appendText (" component design tool.", Font (13.0f));
text2.appendText ("Jucer v" + JUCEApplication::getInstance()->getApplicationVersion()
+ ", " + SystemStats::getJUCEVersion(), Font (14.0f, Font::bold));
addAndMakeVisible (link = new HyperlinkButton ("www.rawmaterialsoftware.com/juce",
URL ("http://www.rawmaterialsoftware.com/juce")));
link->setFont (Font (10.0f, Font::bold | Font::underlined), true);
}
~AboutPage()
{
deleteAllChildren();
text1.setJustification (Justification::centredTop);
text1.append ("Programmer Julian Storer, seen here demonstrating a beard designed to "
"gain approval from the Linux programming community. Each hair of the beard "
"represents one line of source code from the ", Font (13.0f));
text1.append ("Jucer", Font (13.0f, Font::bold));
text1.append (" component design tool.", Font (13.0f));
text2.setJustification (Justification::centred);
text2.append ("Jucer v" + JUCEApplication::getInstance()->getApplicationVersion()
+ ", " + SystemStats::getJUCEVersion(), Font (12.0f, Font::bold));
addAndMakeVisible (&link);
link.setFont (Font (10.0f, Font::bold | Font::underlined), true);
} }
void paint (Graphics& g) void paint (Graphics& g)
{ {
g.fillAll (Colour (0xffebebeb)); g.fillAll (Colour (0xffebebeb));
g.drawImageWithin (logo, 0, 4, getWidth(), getHeight() - 134,
g.drawImageWithin (logo, 0, 4, getWidth(), getHeight() - 144,
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize,
false); false);
text1.drawWithin (g, 0, getHeight() - 120, getWidth(), 100, Justification::centredTop);
text2.drawWithin (g, 0, getHeight() - 50, getWidth(), 100, Justification::centredTop);
text1.draw (g, Rectangle<int> (12, getHeight() - 130, getWidth() - 24, 100).toFloat());
text2.draw (g, Rectangle<int> (12, getHeight() - 50, getWidth() - 24, 20).toFloat());
} }
void resized() void resized()
{ {
text1.layout (getWidth() - 24, Justification::topLeft, false);
text2.layout (getWidth() - 24, Justification::centred, false);
link->setSize (100, 22);
link->changeWidthToFitText();
link->setTopLeftPosition ((getWidth() - link->getWidth()) / 2, getHeight() - link->getHeight() - 10);
link.setSize (100, 22);
link.changeWidthToFitText();
link.setTopLeftPosition ((getWidth() - link.getWidth()) / 2, getHeight() - link.getHeight() - 10);
} }
private:
HyperlinkButton link;
Image logo;
AttributedString text1, text2;
}; };


+ 69
- 481
modules/juce_graphics/fonts/juce_AttributedString.cpp View File

@@ -89,546 +89,134 @@ AttributedString& AttributedString::operator= (const AttributedString& other)
return *this; return *this;
} }
AttributedString::~AttributedString() {}
void AttributedString::setText (const String& other)
{
text = other;
}
void AttributedString::setJustification (const Justification& newJustification) noexcept
{
justification = newJustification;
}
void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept
{
wordWrap = newWordWrap;
}
void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept
{
readingDirection = newReadingDirection;
}
void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
{
lineSpacing = newLineSpacing;
}
void AttributedString::setColour (const Range<int>& range, const Colour& colour)
{
attributes.add (new Attribute (range, colour));
}
void AttributedString::setFont (const Range<int>& range, const Font& font)
{
attributes.add (new Attribute (range, font));
}
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
{
if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
{
if (! g.getInternalContext()->drawTextLayout (*this, area))
{
GlyphLayout layout;
layout.setText (*this, area.getWidth());
layout.draw (g, area);
}
}
}
//==============================================================================
GlyphLayout::Glyph::Glyph (const int glyphCode_, const Point<float>& anchor_) noexcept
: glyphCode (glyphCode_), anchor (anchor_)
{
}
GlyphLayout::Glyph::~Glyph() {}
//==============================================================================
GlyphLayout::Run::Run()
: colour (0xff000000)
{
}
GlyphLayout::Run::Run (const Range<int>& range, const int numGlyphsToPreallocate)
: stringRange (range), colour (0xff000000)
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
AttributedString::AttributedString (AttributedString&& other) noexcept
: text (static_cast <String&&> (other.text)),
lineSpacing (other.lineSpacing),
justification (other.justification),
wordWrap (other.wordWrap),
readingDirection (other.readingDirection),
attributes (static_cast <OwnedArray<Attribute>&&> (other.attributes))
{ {
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
} }
GlyphLayout::Run::~Run() {}
GlyphLayout::Glyph& GlyphLayout::Run::getGlyph (const int index) const
AttributedString& AttributedString::operator= (AttributedString&& other) noexcept
{ {
return *glyphs.getUnchecked (index);
text = static_cast <String&&> (other.text);
lineSpacing = other.lineSpacing;
justification = other.justification;
wordWrap = other.wordWrap;
readingDirection = other.readingDirection;
attributes = static_cast <OwnedArray<Attribute>&&> (other.attributes);
return *this;
} }
#endif
void GlyphLayout::Run::ensureStorageAllocated (int numGlyphsNeeded)
{
glyphs.ensureStorageAllocated (numGlyphsNeeded);
}
AttributedString::~AttributedString() {}
void GlyphLayout::Run::setStringRange (const Range<int>& newStringRange) noexcept
void AttributedString::setText (const String& other)
{ {
stringRange = newStringRange;
text = other;
} }
void GlyphLayout::Run::setFont (const Font& newFont)
void AttributedString::append (const String& textToAppend)
{ {
font = newFont;
text += textToAppend;
} }
void GlyphLayout::Run::setColour (const Colour& newColour) noexcept
void AttributedString::append (const String& textToAppend, const Font& font)
{ {
colour = newColour;
}
const int oldLength = text.length();
const int newLength = textToAppend.length();
void GlyphLayout::Run::addGlyph (Glyph* glyph)
{
glyphs.add (glyph);
text += textToAppend;
setFont (Range<int> (oldLength, oldLength + newLength), font);
} }
//==============================================================================
GlyphLayout::Line::Line() noexcept
: ascent (0.0f), descent (0.0f), leading (0.0f)
void AttributedString::append (const String& textToAppend, const Colour& colour)
{ {
}
const int oldLength = text.length();
const int newLength = textToAppend.length();
GlyphLayout::Line::Line (const Range<int>& stringRange_, const Point<float>& lineOrigin_,
const float ascent_, const float descent_, const float leading_,
const int numRunsToPreallocate)
: stringRange (stringRange_), lineOrigin (lineOrigin_),
ascent (ascent_), descent (descent_), leading (leading_)
{
runs.ensureStorageAllocated (numRunsToPreallocate);
text += textToAppend;
setColour (Range<int> (oldLength, oldLength + newLength), colour);
} }
GlyphLayout::Line::~Line()
void AttributedString::append (const String& textToAppend, const Font& font, const Colour& colour)
{ {
}
const int oldLength = text.length();
const int newLength = textToAppend.length();
GlyphLayout::Run& GlyphLayout::Line::getRun (const int index) const noexcept
{
return *runs.getUnchecked (index);
text += textToAppend;
setFont (Range<int> (oldLength, oldLength + newLength), font);
setColour (Range<int> (oldLength, oldLength + newLength), colour);
} }
void GlyphLayout::Line::setStringRange (const Range<int>& newStringRange) noexcept
void AttributedString::clear()
{ {
stringRange = newStringRange;
text = String::empty;
attributes.clear();
} }
void GlyphLayout::Line::setLineOrigin (const Point<float>& newLineOrigin) noexcept
{
lineOrigin = newLineOrigin;
}
void GlyphLayout::Line::setLeading (float newLeading) noexcept
{
leading = newLeading;
}
void GlyphLayout::Line::increaseAscentDescent (float newAscent, float newDescent) noexcept
void AttributedString::setJustification (const Justification& newJustification) noexcept
{ {
ascent = jmax (ascent, newAscent);
descent = jmax (descent, newDescent);
justification = newJustification;
} }
void GlyphLayout::Line::addRun (Run* run)
void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept
{ {
runs.add (run);
wordWrap = newWordWrap;
} }
//==============================================================================
GlyphLayout::GlyphLayout()
: width (0), justification (Justification::topLeft)
void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept
{ {
readingDirection = newReadingDirection;
} }
GlyphLayout::~GlyphLayout()
void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
{ {
lineSpacing = newLineSpacing;
} }
void GlyphLayout::setText (const AttributedString& text, float maxWidth)
void AttributedString::setColour (const Range<int>& range, const Colour& colour)
{ {
lines.clear();
width = maxWidth;
justification = text.getJustification();
if (! createNativeLayout (text))
createStandardLayout (text);
attributes.add (new Attribute (range, colour));
} }
float GlyphLayout::getHeight() const noexcept
void AttributedString::setColour (const Colour& colour)
{ {
const Line* const lastLine = lines.getLast();
for (int i = attributes.size(); --i >= 0;)
if (attributes.getUnchecked(i)->getColour() != nullptr)
attributes.remove (i);
return lastLine != nullptr ? lastLine->getLineOrigin().getY() + lastLine->getDescent()
: 0;
setColour (Range<int> (0, text.length()), colour);
} }
GlyphLayout::Line& GlyphLayout::getLine (const int index) const
void AttributedString::setFont (const Range<int>& range, const Font& font)
{ {
return *lines[index];
attributes.add (new Attribute (range, font));
} }
void GlyphLayout::ensureStorageAllocated (int numLinesNeeded)
void AttributedString::setFont (const Font& font)
{ {
lines.ensureStorageAllocated (numLinesNeeded);
}
for (int i = attributes.size(); --i >= 0;)
if (attributes.getUnchecked(i)->getFont() != nullptr)
attributes.remove (i);
void GlyphLayout::addLine (Line* line)
{
lines.add (line);
setFont (Range<int> (0, text.length()), font);
} }
void GlyphLayout::draw (Graphics& g, const Rectangle<float>& area) const
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
{ {
const Point<float> origin (justification.appliedToRectangle (Rectangle<float> (0, 0, width, getHeight()), area).getPosition());
LowLevelGraphicsContext& context = *g.getInternalContext();
for (int i = 0; i < getNumLines(); ++i)
if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
{ {
const Line& line = getLine (i);
const Point<float> lineOrigin (origin + line.getLineOrigin());
for (int j = 0; j < line.getNumRuns(); ++j)
if (! g.getInternalContext()->drawTextLayout (*this, area))
{ {
const Run& run = line.getRun (j);
context.setFont (run.getFont());
context.setFill (run.getColour());
for (int k = 0; k < run.getNumGlyphs(); ++k)
{
const Glyph& glyph = run.getGlyph (k);
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
lineOrigin.y + glyph.anchor.y));
}
TextLayout layout;
layout.createLayout (*this, area.getWidth());
layout.draw (g, area);
} }
} }
} }
//==============================================================================
namespace GlyphLayoutHelpers
{
struct FontAndColour
{
FontAndColour (const Font* font_) noexcept : font (font_), colour (0xff000000) {}
const Font* font;
Colour colour;
bool operator!= (const FontAndColour& other) const noexcept
{
return (font != other.font && *font != *other.font) || colour != other.colour;
}
};
struct RunAttribute
{
RunAttribute (const FontAndColour& fontAndColour_, const Range<int>& range_) noexcept
: fontAndColour (fontAndColour_), range (range_)
{}
FontAndColour fontAndColour;
Range<int> range;
};
struct Token
{
Token (const String& t, const Font& f, const Colour& c, const bool isWhitespace_)
: text (t), font (f), colour (c),
area (font.getStringWidth (t), roundToInt (f.getHeight())),
isWhitespace (isWhitespace_),
isNewLine (t.containsChar ('\n') || t.containsChar ('\r'))
{}
const String text;
const Font font;
const Colour colour;
Rectangle<int> area;
int line, lineHeight;
const bool isWhitespace, isNewLine;
private:
Token& operator= (const Token&);
};
class TokenList
{
public:
TokenList() noexcept : totalLines (0) {}
void createLayout (const AttributedString& text, GlyphLayout& glyphLayout)
{
tokens.ensureStorageAllocated (64);
glyphLayout.ensureStorageAllocated (totalLines);
addTextRuns (text);
layout ((int) glyphLayout.getWidth());
int charPosition = 0;
int lineStartPosition = 0;
int runStartPosition = 0;
GlyphLayout::Line* glyphLine = new GlyphLayout::Line();
GlyphLayout::Run* glyphRun = new GlyphLayout::Run();
for (int i = 0; i < tokens.size(); ++i)
{
const Token* const t = tokens.getUnchecked (i);
const Point<float> tokenPos (t->area.getPosition().toFloat());
Array <int> newGlyphs;
Array <float> xOffsets;
t->font.getGlyphPositions (t->text.trimEnd(), newGlyphs, xOffsets);
glyphRun->ensureStorageAllocated (glyphRun->getNumGlyphs() + newGlyphs.size());
for (int j = 0; j < newGlyphs.size(); ++j)
{
if (charPosition == lineStartPosition)
glyphLine->setLineOrigin (tokenPos.translated (0, t->font.getAscent()));
glyphRun->addGlyph (new GlyphLayout::Glyph (newGlyphs.getUnchecked(j),
Point<float> (tokenPos.getX() + xOffsets.getUnchecked (j), 0)));
++charPosition;
}
if (t->isWhitespace || t->isNewLine)
++charPosition;
const Token* const nextToken = tokens [i + 1];
if (nextToken == nullptr) // this is the last token
{
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
glyphLine->setStringRange (Range<int> (lineStartPosition, charPosition));
glyphLayout.addLine (glyphLine);
}
else
{
if (t->font != nextToken->font || t->colour != nextToken->colour)
{
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
runStartPosition = charPosition;
glyphRun = new GlyphLayout::Run();
}
if (t->line != nextToken->line)
{
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
glyphLine->setStringRange (Range<int> (lineStartPosition, charPosition));
glyphLayout.addLine (glyphLine);
runStartPosition = charPosition;
lineStartPosition = charPosition;
glyphLine = new GlyphLayout::Line();
glyphRun = new GlyphLayout::Run();
}
}
}
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
{
const int totalW = (int) glyphLayout.getWidth();
for (int i = 0; i < totalLines; ++i)
{
const int lineW = getLineWidth (i);
float dx = 0;
if ((text.getJustification().getFlags() & Justification::right) != 0)
dx = (float) (totalW - lineW);
else
dx = (totalW - lineW) / 2.0f;
GlyphLayout::Line& glyphLine = glyphLayout.getLine (i);
glyphLine.setLineOrigin (glyphLine.getLineOrigin().translated (dx, 0));
}
}
}
private:
static void addRun (GlyphLayout::Line* glyphLine, GlyphLayout::Run* glyphRun,
const Token* const t, const int start, const int end)
{
glyphRun->setStringRange (Range<int> (start, end));
glyphRun->setFont (t->font);
glyphRun->setColour (t->colour);
glyphLine->increaseAscentDescent (t->font.getAscent(), t->font.getDescent());
glyphLine->addRun (glyphRun);
}
void appendText (const AttributedString& text, const Range<int>& stringRange,
const Font& font, const Colour& colour)
{
String stringText (text.getText().substring(stringRange.getStart(), stringRange.getEnd()));
String::CharPointerType t (stringText.getCharPointer());
String currentString;
int lastCharType = 0;
for (;;)
{
const juce_wchar c = t.getAndAdvance();
if (c == 0)
break;
int charType;
if (c == '\r' || c == '\n')
charType = 0;
else if (CharacterFunctions::isWhitespace (c))
charType = 2;
else
charType = 1;
if (charType == 0 || charType != lastCharType)
{
if (currentString.isNotEmpty())
tokens.add (new Token (currentString, font, colour,
lastCharType == 2 || lastCharType == 0));
currentString = String::charToString (c);
if (c == '\r' && *t == '\n')
currentString += t.getAndAdvance();
}
else
{
currentString += c;
}
lastCharType = charType;
}
if (currentString.isNotEmpty())
tokens.add (new Token (currentString, font, colour, lastCharType == 2));
}
void layout (const int maxWidth)
{
int x = 0, y = 0, h = 0;
int i;
for (i = 0; i < tokens.size(); ++i)
{
Token* const t = tokens.getUnchecked(i);
t->area.setPosition (x, y);
t->line = totalLines;
x += t->area.getWidth();
h = jmax (h, t->area.getHeight());
const Token* nextTok = tokens[i + 1];
if (nextTok == 0)
break;
if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->area.getWidth() > maxWidth))
{
setLastLineHeight (i + 1, h);
x = 0;
y += h;
h = 0;
++totalLines;
}
}
setLastLineHeight (jmin (i + 1, tokens.size()), h);
++totalLines;
}
void setLastLineHeight (int i, const int height) noexcept
{
while (--i >= 0)
{
Token* const tok = tokens.getUnchecked (i);
if (tok->line == totalLines)
tok->lineHeight = height;
else
break;
}
}
int getLineWidth (const int lineNumber) const noexcept
{
int maxW = 0;
for (int i = tokens.size(); --i >= 0;)
{
const Token* const t = tokens.getUnchecked (i);
if (t->line == lineNumber && ! t->isWhitespace)
maxW = jmax (maxW, t->area.getRight());
}
return maxW;
}
void addTextRuns (const AttributedString& text)
{
Font defaultFont;
Array<RunAttribute> runAttributes;
{
const int stringLength = text.getText().length();
int rangeStart = 0;
FontAndColour lastFontAndColour (nullptr);
// Iterate through every character in the string
for (int i = 0; i < stringLength; ++i)
{
FontAndColour newFontAndColour (&defaultFont);
const int numCharacterAttributes = text.getNumAttributes();
for (int j = 0; j < numCharacterAttributes; ++j)
{
const AttributedString::Attribute* const attr = text.getAttribute (j);
// Check if the current character falls within the range of a font attribute
if (attr->getFont() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
newFontAndColour.font = attr->getFont();
// Check if the current character falls within the range of a foreground colour attribute
if (attr->getColour() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
newFontAndColour.colour = *attr->getColour();
}
if (i > 0 && (newFontAndColour != lastFontAndColour || i == stringLength - 1))
{
runAttributes.add (RunAttribute (lastFontAndColour,
Range<int> (rangeStart, (i < stringLength - 1) ? i : (i + 1))));
rangeStart = i;
}
lastFontAndColour = newFontAndColour;
}
}
for (int i = 0; i < runAttributes.size(); ++i)
{
const RunAttribute& r = runAttributes.getReference(i);
appendText (text, r.range, *(r.fontAndColour.font), r.fontAndColour.colour);
}
}
OwnedArray<Token> tokens;
int totalLines;
JUCE_DECLARE_NON_COPYABLE (TokenList);
};
}
//==============================================================================
void GlyphLayout::createStandardLayout (const AttributedString& text)
{
GlyphLayoutHelpers::TokenList l;
l.createLayout (text, *this);
}
END_JUCE_NAMESPACE END_JUCE_NAMESPACE

+ 33
- 144
modules/juce_graphics/fonts/juce_AttributedString.h View File

@@ -34,6 +34,8 @@
An attributed string lets you create a string with varied fonts, colours, word-wrapping, An attributed string lets you create a string with varied fonts, colours, word-wrapping,
layout, etc., and draw it using AttributedString::draw(). layout, etc., and draw it using AttributedString::draw().
@see TextLayout
*/ */
class JUCE_API AttributedString class JUCE_API AttributedString
{ {
@@ -44,8 +46,12 @@ public:
/** Creates an attributed string with the given text. */ /** Creates an attributed string with the given text. */
explicit AttributedString (const String& text); explicit AttributedString (const String& text);
AttributedString (const AttributedString& other);
AttributedString& operator= (const AttributedString& other);
AttributedString (const AttributedString&);
AttributedString& operator= (const AttributedString&);
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
AttributedString (AttributedString&&) noexcept;
AttributedString& operator= (AttributedString&&) noexcept;
#endif
/** Destructor. */ /** Destructor. */
~AttributedString(); ~AttributedString();
@@ -54,12 +60,27 @@ public:
/** Returns the complete text of this attributed string. */ /** Returns the complete text of this attributed string. */
const String& getText() const noexcept { return text; } const String& getText() const noexcept { return text; }
/** Sets the text.
This will change the text, but won't affect any of the attributes that have
been added.
/** Replaces all the text.
This will change the text, but won't affect any of the colour or font attributes
that have been added.
*/ */
void setText (const String& newText); void setText (const String& newText);
/** Appends some text (with a default font and colour). */
void append (const String& textToAppend);
/** Appends some text, with a specified font, and the default colour (black). */
void append (const String& textToAppend, const Font& font);
/** Appends some text, with a specified colour, and the default font. */
void append (const String& textToAppend, const Colour& colour);
/** Appends some text, with a specified font and colour. */
void append (const String& textToAppend, const Font& font, const Colour& colour);
/** Resets the string, clearing all text and attributes.
Note that this won't affect global settings like the justification type,
word-wrap mode, etc.
*/
void clear();
//============================================================================== //==============================================================================
/** Draws this string within the given area. /** Draws this string within the given area.
The layout of the string within the rectangle is controlled by the justification The layout of the string within the rectangle is controlled by the justification
@@ -143,7 +164,7 @@ public:
/** If this attribute specifies a colour, this returns it; otherwise it returns nullptr. */ /** If this attribute specifies a colour, this returns it; otherwise it returns nullptr. */
const Colour* getColour() const noexcept { return colour; } const Colour* getColour() const noexcept { return colour; }
/** The range of characters to which this attribute should be applied. */
/** The range of characters to which this attribute will be applied. */
const Range<int> range; const Range<int> range;
private: private:
@@ -165,9 +186,15 @@ public:
/** Adds a colour attribute for the specified range. */ /** Adds a colour attribute for the specified range. */
void setColour (const Range<int>& range, const Colour& colour); void setColour (const Range<int>& range, const Colour& colour);
/** Removes all existing colour attributes, and applies this colour to the whole string. */
void setColour (const Colour& colour);
/** Adds a font attribute for the specified range. */ /** Adds a font attribute for the specified range. */
void setFont (const Range<int>& range, const Font& font); void setFont (const Range<int>& range, const Font& font);
/** Removes all existing font attributes, and applies this font to the whole string. */
void setFont (const Font& font);
private: private:
String text; String text;
float lineSpacing; float lineSpacing;
@@ -177,142 +204,4 @@ private:
OwnedArray<Attribute> attributes; OwnedArray<Attribute> attributes;
}; };
//==============================================================================
/**
*/
class JUCE_API GlyphLayout
{
public:
/** Creates an empty layout. */
GlyphLayout();
/** Destructor. */
~GlyphLayout();
//==============================================================================
/** Creates a layout from the given attributed string.
This will replace any data that is currently stored in the layout.
*/
void setText (const AttributedString& text, float maxWidth);
/** Draws the layout within the specified area.
The position of the text within the rectangle is controlled by the justification
flags set in the original AttributedString that was used to create this layout.
*/
void draw (Graphics& g, const Rectangle<float>& area) const;
//==============================================================================
/** A positioned glyph. */
class JUCE_API Glyph
{
public:
Glyph (int glyphCode, const Point<float>& anchor) noexcept;
~Glyph();
const int glyphCode;
const Point<float> anchor;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Glyph);
};
//==============================================================================
/** A sequence of glyphs. */
class JUCE_API Run
{
public:
Run();
Run (const Range<int>& range, int numGlyphsToPreallocate);
~Run();
int getNumGlyphs() const noexcept { return glyphs.size(); }
const Font& getFont() const noexcept { return font; }
const Colour& getColour() const { return colour; }
Glyph& getGlyph (int index) const;
void setStringRange (const Range<int>& newStringRange) noexcept;
void setFont (const Font& newFont);
void setColour (const Colour& newColour) noexcept;
void addGlyph (Glyph* glyph);
void ensureStorageAllocated (int numGlyphsNeeded);
private:
OwnedArray<Glyph> glyphs;
Range<int> stringRange;
Font font;
Colour colour;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Run);
};
//==============================================================================
/** A line containing a sequence of glyph-runs. */
class JUCE_API Line
{
public:
Line() noexcept;
Line (const Range<int>& stringRange, const Point<float>& lineOrigin,
float ascent, float descent, float leading, int numRunsToPreallocate);
~Line();
const Point<float>& getLineOrigin() const noexcept { return lineOrigin; }
float getAscent() const noexcept { return ascent; }
float getDescent() const noexcept { return descent; }
float getLeading() const noexcept { return leading; }
int getNumRuns() const noexcept { return runs.size(); }
Run& getRun (int index) const noexcept;
void setStringRange (const Range<int>& newStringRange) noexcept;
void setLineOrigin (const Point<float>& newLineOrigin) noexcept;
void setLeading (float newLeading) noexcept;
void increaseAscentDescent (float newAscent, float newDescent) noexcept;
void addRun (Run* run);
private:
OwnedArray<Run> runs;
Range<int> stringRange;
Point<float> lineOrigin;
float ascent, descent, leading;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Line);
};
//==============================================================================
/** Returns the maximum width of the content. */
float getWidth() const noexcept { return width; }
/** Returns the maximum height of the content. */
float getHeight() const noexcept;
/** Returns the number of lines in the layout. */
int getNumLines() const noexcept { return lines.size(); }
/** Returns one of the lines. */
Line& getLine (int index) const;
/** Adds a line to the layout. The object passed-in will be owned and deleted by the layout
when it is no longer needed.
*/
void addLine (Line* line);
/** Pre-allocates space for the specified number of lines. */
void ensureStorageAllocated (int numLinesNeeded);
private:
OwnedArray<Line> lines;
float width;
Justification justification;
void createStandardLayout (const AttributedString&);
bool createNativeLayout (const AttributedString&);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphLayout);
};
#endif // __JUCE_ATTRIBUTEDSTRING_JUCEHEADER__ #endif // __JUCE_ATTRIBUTEDSTRING_JUCEHEADER__

+ 471
- 233
modules/juce_graphics/fonts/juce_TextLayout.cpp View File

@@ -25,351 +25,589 @@
BEGIN_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE
TextLayout::Glyph::Glyph (const int glyphCode_, const Point<float>& anchor_, float width_) noexcept
: glyphCode (glyphCode_), anchor (anchor_), width (width_)
{
}
TextLayout::Glyph::Glyph (const Glyph& other) noexcept
: glyphCode (other.glyphCode), anchor (other.anchor), width (other.width)
{
}
TextLayout::Glyph& TextLayout::Glyph::operator= (const Glyph& other) noexcept
{
glyphCode = other.glyphCode;
anchor = other.anchor;
width = other.width;
return *this;
}
TextLayout::Glyph::~Glyph() noexcept {}
//============================================================================== //==============================================================================
class TextLayout::Token
TextLayout::Run::Run() noexcept
: colour (0xff000000)
{ {
public:
Token (const String& t,
const Font& f,
const bool isWhitespace_)
: text (t),
font (f),
x(0),
y(0),
isWhitespace (isWhitespace_)
{
w = font.getStringWidth (t);
h = roundToInt (f.getHeight());
isNewLine = t.containsChar ('\n') || t.containsChar ('\r');
}
}
Token (const Token& other)
: text (other.text),
font (other.font),
x (other.x),
y (other.y),
w (other.w),
h (other.h),
line (other.line),
lineHeight (other.lineHeight),
isWhitespace (other.isWhitespace),
isNewLine (other.isNewLine)
{
}
TextLayout::Run::Run (const Range<int>& range, const int numGlyphsToPreallocate)
: colour (0xff000000), stringRange (range)
{
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
}
TextLayout::Run::Run (const Run& other)
: font (other.font),
colour (other.colour),
glyphs (other.glyphs),
stringRange (other.stringRange)
{
}
TextLayout::Run::~Run() noexcept {}
void draw (Graphics& g,
const int xOffset,
const int yOffset)
//==============================================================================
TextLayout::Line::Line() noexcept
: ascent (0.0f), descent (0.0f), leading (0.0f)
{
}
TextLayout::Line::Line (const Range<int>& stringRange_, const Point<float>& lineOrigin_,
const float ascent_, const float descent_, const float leading_,
const int numRunsToPreallocate)
: stringRange (stringRange_), lineOrigin (lineOrigin_),
ascent (ascent_), descent (descent_), leading (leading_)
{
runs.ensureStorageAllocated (numRunsToPreallocate);
}
TextLayout::Line::Line (const Line& other)
: stringRange (other.stringRange), lineOrigin (other.lineOrigin),
ascent (other.ascent), descent (other.descent), leading (other.leading)
{
runs.addCopiesOf (other.runs);
}
TextLayout::Line::~Line() noexcept
{
}
Range<float> TextLayout::Line::getLineBoundsX() const noexcept
{
Range<float> range;
bool isFirst = true;
for (int i = runs.size(); --i >= 0;)
{ {
if (! isWhitespace)
const Run* run = runs.getUnchecked(i);
jassert (run != nullptr);
if (run->glyphs.size() > 0)
{ {
g.setFont (font);
g.drawSingleLineText (text.trimEnd(),
xOffset + x,
yOffset + y + (lineHeight - h)
+ roundToInt (font.getAscent()));
}
}
float minX = run->glyphs.getReference(0).anchor.x;
float maxX = minX;
String text;
Font font;
int x, y, w, h;
int line, lineHeight;
bool isWhitespace, isNewLine;
for (int j = run->glyphs.size(); --j > 0;)
{
const Glyph& glyph = run->glyphs.getReference (j);
const float x = glyph.anchor.x;
minX = jmin (minX, x);
maxX = jmax (maxX, x + glyph.width);
}
private:
JUCE_LEAK_DETECTOR (Token);
};
if (isFirst)
{
isFirst = false;
range = Range<float> (minX, maxX);
}
else
{
range = range.getUnionWith (Range<float> (minX, maxX));
}
}
}
return range + lineOrigin.x;
}
//============================================================================== //==============================================================================
TextLayout::TextLayout() TextLayout::TextLayout()
: totalLines (0)
: width (0), justification (Justification::topLeft)
{ {
tokens.ensureStorageAllocated (64);
} }
TextLayout::TextLayout (const String& text, const Font& font)
: totalLines (0)
TextLayout::TextLayout (const TextLayout& other)
: width (other.width),
justification (other.justification)
{ {
tokens.ensureStorageAllocated (64);
appendText (text, font);
lines.addCopiesOf (other.lines);
} }
TextLayout::TextLayout (const TextLayout& other)
: totalLines (0)
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
TextLayout::TextLayout (TextLayout&& other) noexcept
: lines (static_cast <OwnedArray<Line>&&> (other.lines)),
width (other.width),
justification (other.justification)
{ {
*this = other;
} }
TextLayout& TextLayout::operator= (const TextLayout& other)
TextLayout& TextLayout::operator= (TextLayout&& other) noexcept
{ {
if (this != &other)
{
clear();
totalLines = other.totalLines;
tokens.addCopiesOf (other.tokens);
}
lines = static_cast <OwnedArray<Line>&&> (other.lines);
width = other.width;
justification = other.justification;
return *this;
}
#endif
TextLayout& TextLayout::operator= (const TextLayout& other)
{
width = other.width;
justification = other.justification;
lines.clear();
lines.addCopiesOf (other.lines);
return *this; return *this;
} }
TextLayout::~TextLayout() TextLayout::~TextLayout()
{ {
clear();
} }
//==============================================================================
void TextLayout::clear()
float TextLayout::getHeight() const noexcept
{ {
tokens.clear();
totalLines = 0;
const Line* const lastLine = lines.getLast();
return lastLine != nullptr ? lastLine->lineOrigin.y + lastLine->descent
: 0;
} }
bool TextLayout::isEmpty() const
TextLayout::Line& TextLayout::getLine (const int index) const
{ {
return tokens.size() == 0;
return *lines[index];
} }
void TextLayout::appendText (const String& text, const Font& font)
void TextLayout::ensureStorageAllocated (int numLinesNeeded)
{ {
String::CharPointerType t (text.getCharPointer());
String currentString;
int lastCharType = 0;
lines.ensureStorageAllocated (numLinesNeeded);
}
void TextLayout::addLine (Line* line)
{
lines.add (line);
}
for (;;)
void TextLayout::draw (Graphics& g, const Rectangle<float>& area) const
{
const Point<float> origin (justification.appliedToRectangle (Rectangle<float> (0, 0, width, getHeight()), area).getPosition());
LowLevelGraphicsContext& context = *g.getInternalContext();
for (int i = 0; i < getNumLines(); ++i)
{ {
const juce_wchar c = t.getAndAdvance();
if (c == 0)
break;
const Line& line = getLine (i);
const Point<float> lineOrigin (origin + line.lineOrigin);
int charType;
if (c == '\r' || c == '\n')
for (int j = 0; j < line.runs.size(); ++j)
{ {
charType = 0;
}
else if (CharacterFunctions::isWhitespace (c))
{
charType = 2;
}
else
{
charType = 1;
}
const Run* const run = line.runs.getUnchecked (j);
jassert (run != nullptr);
context.setFont (run->font);
context.setFill (run->colour);
if (charType == 0 || charType != lastCharType)
{
if (currentString.isNotEmpty())
for (int k = 0; k < run->glyphs.size(); ++k)
{ {
tokens.add (new Token (currentString, font,
lastCharType == 2 || lastCharType == 0));
const Glyph& glyph = run->glyphs.getReference (k);
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
lineOrigin.y + glyph.anchor.y));
} }
currentString = String::charToString (c);
if (c == '\r' && *t == '\n')
currentString += t.getAndAdvance();
}
else
{
currentString += c;
} }
lastCharType = charType;
} }
if (currentString.isNotEmpty())
tokens.add (new Token (currentString, font, lastCharType == 2));
} }
void TextLayout::setText (const String& text, const Font& font)
void TextLayout::createLayout (const AttributedString& text, float maxWidth)
{ {
clear();
appendText (text, font);
lines.clear();
width = maxWidth;
justification = text.getJustification();
if (! createNativeLayout (text))
createStandardLayout (text);
recalculateWidth();
} }
//============================================================================== //==============================================================================
void TextLayout::layout (int maxWidth,
const Justification& justification,
const bool attemptToBalanceLineLengths)
namespace TextLayoutHelpers
{ {
if (attemptToBalanceLineLengths)
struct FontAndColour
{
FontAndColour (const Font* font_) noexcept : font (font_), colour (0xff000000) {}
const Font* font;
Colour colour;
bool operator!= (const FontAndColour& other) const noexcept
{
return (font != other.font && *font != *other.font) || colour != other.colour;
}
};
struct RunAttribute
{ {
const int originalW = maxWidth;
int bestWidth = maxWidth;
float bestLineProportion = 0.0f;
RunAttribute (const FontAndColour& fontAndColour_, const Range<int>& range_) noexcept
: fontAndColour (fontAndColour_), range (range_)
{}
while (maxWidth > originalW / 2)
FontAndColour fontAndColour;
Range<int> range;
};
struct Token
{
Token (const String& t, const Font& f, const Colour& c, const bool isWhitespace_)
: text (t), font (f), colour (c),
area (font.getStringWidth (t), roundToInt (f.getHeight())),
isWhitespace (isWhitespace_),
isNewLine (t.containsChar ('\n') || t.containsChar ('\r'))
{}
const String text;
const Font font;
const Colour colour;
Rectangle<int> area;
int line, lineHeight;
const bool isWhitespace, isNewLine;
private:
Token& operator= (const Token&);
};
class TokenList
{
public:
TokenList() noexcept : totalLines (0) {}
void createLayout (const AttributedString& text, TextLayout& layout)
{ {
layout (maxWidth, justification, false);
tokens.ensureStorageAllocated (64);
layout.ensureStorageAllocated (totalLines);
if (getNumLines() <= 1)
return;
addTextRuns (text);
const int lastLineW = getLineWidth (getNumLines() - 1);
const int lastButOneLineW = getLineWidth (getNumLines() - 2);
layoutRuns ((int) layout.getWidth());
const float prop = lastLineW / (float) lastButOneLineW;
int charPosition = 0;
int lineStartPosition = 0;
int runStartPosition = 0;
if (prop > 0.9f)
return;
TextLayout::Line* glyphLine = new TextLayout::Line();
TextLayout::Run* glyphRun = new TextLayout::Run();
if (prop > bestLineProportion)
for (int i = 0; i < tokens.size(); ++i)
{ {
bestLineProportion = prop;
bestWidth = maxWidth;
}
const Token* const t = tokens.getUnchecked (i);
const Point<float> tokenPos (t->area.getPosition().toFloat());
maxWidth -= 10;
}
Array <int> newGlyphs;
Array <float> xOffsets;
t->font.getGlyphPositions (t->text.trimEnd(), newGlyphs, xOffsets);
layout (bestWidth, justification, false);
}
else
{
int x = 0;
int y = 0;
int h = 0;
totalLines = 0;
int i;
glyphRun->glyphs.ensureStorageAllocated (glyphRun->glyphs.size() + newGlyphs.size());
for (i = 0; i < tokens.size(); ++i)
{
Token* const t = tokens.getUnchecked(i);
t->x = x;
t->y = y;
t->line = totalLines;
x += t->w;
h = jmax (h, t->h);
for (int j = 0; j < newGlyphs.size(); ++j)
{
if (charPosition == lineStartPosition)
glyphLine->lineOrigin = tokenPos.translated (0, t->font.getAscent());
const float x = xOffsets.getUnchecked (j);
glyphRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked(j),
Point<float> (tokenPos.getX() + x, 0),
xOffsets.getUnchecked (j + 1) - x));
++charPosition;
}
const Token* nextTok = tokens [i + 1];
if (t->isWhitespace || t->isNewLine)
++charPosition;
if (nextTok == 0)
break;
const Token* const nextToken = tokens [i + 1];
if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->w > maxWidth))
if (nextToken == nullptr) // this is the last token
{
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
glyphLine->stringRange = Range<int> (lineStartPosition, charPosition);
layout.addLine (glyphLine);
}
else
{
if (t->font != nextToken->font || t->colour != nextToken->colour)
{
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
runStartPosition = charPosition;
glyphRun = new TextLayout::Run();
}
if (t->line != nextToken->line)
{
addRun (glyphLine, glyphRun, t, runStartPosition, charPosition);
glyphLine->stringRange = Range<int> (lineStartPosition, charPosition);
layout.addLine (glyphLine);
runStartPosition = charPosition;
lineStartPosition = charPosition;
glyphLine = new TextLayout::Line();
glyphRun = new TextLayout::Run();
}
}
}
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
{ {
// finished a line, so go back and update the heights of the things on it
for (int j = i; j >= 0; --j)
const int totalW = (int) layout.getWidth();
for (int i = 0; i < totalLines; ++i)
{ {
Token* const tok = tokens.getUnchecked(j);
const int lineW = getLineWidth (i);
float dx = 0;
if (tok->line == totalLines)
tok->lineHeight = h;
if ((text.getJustification().getFlags() & Justification::right) != 0)
dx = (float) (totalW - lineW);
else else
break;
}
dx = (totalW - lineW) / 2.0f;
x = 0;
y += h;
h = 0;
++totalLines;
TextLayout::Line& glyphLine = layout.getLine (i);
glyphLine.lineOrigin.x += dx;
}
} }
} }
// finished a line, so go back and update the heights of the things on it
for (int j = jmin (i, tokens.size() - 1); j >= 0; --j)
private:
static void addRun (TextLayout::Line* glyphLine, TextLayout::Run* glyphRun,
const Token* const t, const int start, const int end)
{ {
Token* const t = tokens.getUnchecked(j);
if (t->line == totalLines)
t->lineHeight = h;
else
break;
glyphRun->stringRange = Range<int> (start, end);
glyphRun->font = t->font;
glyphRun->colour = t->colour;
glyphLine->ascent = jmax (glyphLine->ascent, t->font.getAscent());
glyphLine->descent = jmax (glyphLine->descent, t->font.getDescent());
glyphLine->runs.add (glyphRun);
} }
++totalLines;
if (! justification.testFlags (Justification::left))
void appendText (const AttributedString& text, const Range<int>& stringRange,
const Font& font, const Colour& colour)
{ {
int totalW = getWidth();
String stringText (text.getText().substring (stringRange.getStart(), stringRange.getEnd()));
String::CharPointerType t (stringText.getCharPointer());
String currentString;
int lastCharType = 0;
for (i = totalLines; --i >= 0;)
for (;;)
{ {
const int lineW = getLineWidth (i);
const juce_wchar c = t.getAndAdvance();
if (c == 0)
break;
int charType;
if (c == '\r' || c == '\n')
charType = 0;
else if (CharacterFunctions::isWhitespace (c))
charType = 2;
else
charType = 1;
if (charType == 0 || charType != lastCharType)
{
if (currentString.isNotEmpty())
tokens.add (new Token (currentString, font, colour,
lastCharType == 2 || lastCharType == 0));
int dx = 0;
if (justification.testFlags (Justification::horizontallyCentred))
dx = (totalW - lineW) / 2;
else if (justification.testFlags (Justification::right))
dx = totalW - lineW;
currentString = String::charToString (c);
for (int j = tokens.size(); --j >= 0;)
if (c == '\r' && *t == '\n')
currentString += t.getAndAdvance();
}
else
{ {
Token* const t = tokens.getUnchecked(j);
currentString += c;
}
lastCharType = charType;
}
if (currentString.isNotEmpty())
tokens.add (new Token (currentString, font, colour, lastCharType == 2));
}
void layoutRuns (const int maxWidth)
{
int x = 0, y = 0, h = 0;
int i;
for (i = 0; i < tokens.size(); ++i)
{
Token* const t = tokens.getUnchecked(i);
t->area.setPosition (x, y);
t->line = totalLines;
x += t->area.getWidth();
h = jmax (h, t->area.getHeight());
if (t->line == i)
t->x += dx;
const Token* nextTok = tokens[i + 1];
if (nextTok == 0)
break;
if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->area.getWidth() > maxWidth))
{
setLastLineHeight (i + 1, h);
x = 0;
y += h;
h = 0;
++totalLines;
} }
} }
setLastLineHeight (jmin (i + 1, tokens.size()), h);
++totalLines;
} }
}
}
//==============================================================================
int TextLayout::getLineWidth (const int lineNumber) const
{
int maxW = 0;
void setLastLineHeight (int i, const int height) noexcept
{
while (--i >= 0)
{
Token* const tok = tokens.getUnchecked (i);
for (int i = tokens.size(); --i >= 0;)
{
const Token* const t = tokens.getUnchecked(i);
if (tok->line == totalLines)
tok->lineHeight = height;
else
break;
}
}
if (t->line == lineNumber && ! t->isWhitespace)
maxW = jmax (maxW, t->x + t->w);
}
int getLineWidth (const int lineNumber) const noexcept
{
int maxW = 0;
return maxW;
}
for (int i = tokens.size(); --i >= 0;)
{
const Token* const t = tokens.getUnchecked (i);
int TextLayout::getWidth() const
{
int maxW = 0;
if (t->line == lineNumber && ! t->isWhitespace)
maxW = jmax (maxW, t->area.getRight());
}
for (int i = tokens.size(); --i >= 0;)
{
const Token* const t = tokens.getUnchecked(i);
if (! t->isWhitespace)
maxW = jmax (maxW, t->x + t->w);
}
return maxW;
}
return maxW;
void addTextRuns (const AttributedString& text)
{
Font defaultFont;
Array<RunAttribute> runAttributes;
{
const int stringLength = text.getText().length();
int rangeStart = 0;
FontAndColour lastFontAndColour (nullptr);
// Iterate through every character in the string
for (int i = 0; i < stringLength; ++i)
{
FontAndColour newFontAndColour (&defaultFont);
const int numCharacterAttributes = text.getNumAttributes();
for (int j = 0; j < numCharacterAttributes; ++j)
{
const AttributedString::Attribute* const attr = text.getAttribute (j);
// Check if the current character falls within the range of a font attribute
if (attr->getFont() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
newFontAndColour.font = attr->getFont();
// Check if the current character falls within the range of a foreground colour attribute
if (attr->getColour() != nullptr && (i >= attr->range.getStart()) && (i < attr->range.getEnd()))
newFontAndColour.colour = *attr->getColour();
}
if (i > 0 && (newFontAndColour != lastFontAndColour || i == stringLength - 1))
{
runAttributes.add (RunAttribute (lastFontAndColour,
Range<int> (rangeStart, (i < stringLength - 1) ? i : (i + 1))));
rangeStart = i;
}
lastFontAndColour = newFontAndColour;
}
}
for (int i = 0; i < runAttributes.size(); ++i)
{
const RunAttribute& r = runAttributes.getReference(i);
appendText (text, r.range, *(r.fontAndColour.font), r.fontAndColour.colour);
}
}
OwnedArray<Token> tokens;
int totalLines;
JUCE_DECLARE_NON_COPYABLE (TokenList);
};
} }
int TextLayout::getHeight() const
//==============================================================================
void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth)
{ {
int maxH = 0;
const float minimumWidth = maxWidth / 2.0;
float bestWidth = maxWidth;
float bestLineProportion = 0.0f;
for (int i = tokens.size(); --i >= 0;)
while (maxWidth > minimumWidth)
{ {
const Token* const t = tokens.getUnchecked(i);
createLayout (text, maxWidth);
if (getNumLines() < 2)
return;
const float line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength();
const float line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength();
const float prop = jmax (line1, line2) / jmin (line1, line2);
if (! t->isWhitespace)
maxH = jmax (maxH, t->y + t->h);
if (prop > 0.9f)
return;
if (prop > bestLineProportion)
{
bestLineProportion = prop;
bestWidth = maxWidth;
}
maxWidth -= 10.0f;
} }
return maxH;
if (bestWidth != maxWidth)
createLayout (text, bestWidth);
} }
//============================================================================== //==============================================================================
void TextLayout::draw (Graphics& g,
const int xOffset,
const int yOffset) const
void TextLayout::createStandardLayout (const AttributedString& text)
{ {
for (int i = tokens.size(); --i >= 0;)
tokens.getUnchecked(i)->draw (g, xOffset, yOffset);
TextLayoutHelpers::TokenList l;
l.createLayout (text, *this);
} }
void TextLayout::drawWithin (Graphics& g,
int x, int y, int w, int h,
const Justification& justification) const
void TextLayout::recalculateWidth()
{ {
justification.applyToRectangle (x, y, getWidth(), getHeight(),
x, y, w, h);
if (lines.size() > 0)
{
Range<float> range (lines.getFirst()->getLineBoundsX());
draw (g, x, y);
}
int i;
for (i = lines.size(); --i > 0;)
range = range.getUnionWith (lines.getUnchecked(i)->getLineBoundsX());
for (i = lines.size(); --i >= 0;)
lines.getUnchecked(i)->lineOrigin.x -= range.getStart();
width = range.getLength();
}
}
END_JUCE_NAMESPACE END_JUCE_NAMESPACE

+ 105
- 84
modules/juce_graphics/fonts/juce_TextLayout.h View File

@@ -30,124 +30,145 @@
#include "../placement/juce_Justification.h" #include "../placement/juce_Justification.h"
class Graphics; class Graphics;
//============================================================================== //==============================================================================
/** /**
A laid-out arrangement of text.
You can add text in different fonts to a TextLayout object, then call its
layout() method to word-wrap it into lines. The layout can then be drawn
using a graphics context.
A Pre-formatted piece of text, which may contain multiple fonts and colours.
It's handy if you've got a message to display, because you can format it,
measure the extent of the layout, and then create a suitably-sized window
to show it in.
A TextLayout is created from an AttributedString, and once created can be
quickly drawn into a Graphics context.
@see Font, Graphics::drawFittedText, GlyphArrangement
@see AttributedString
*/ */
class JUCE_API TextLayout class JUCE_API TextLayout
{ {
public: public:
//==============================================================================
/** Creates an empty text layout.
Text can then be appended using the appendText() method.
/** Creates an empty layout.
Having created a TextLayout, you can populate it using createLayout() or
createLayoutWithBalancedLineLengths().
*/ */
TextLayout(); TextLayout();
/** Creates a copy of another layout object. */
TextLayout (const TextLayout& other);
/** Creates a text layout from an initial string and font. */
TextLayout (const String& text, const Font& font);
TextLayout (const TextLayout&);
TextLayout& operator= (const TextLayout&);
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
TextLayout (TextLayout&& other) noexcept;
TextLayout& operator= (TextLayout&&) noexcept;
#endif
/** Destructor. */ /** Destructor. */
~TextLayout(); ~TextLayout();
/** Copies another layout onto this one. */
TextLayout& operator= (const TextLayout& layoutToCopy);
//============================================================================== //==============================================================================
/** Clears the layout, removing all its text. */
void clear();
/** Adds a string to the end of the arrangement.
The string will be broken onto new lines wherever it contains
carriage-returns or linefeeds. After adding it, you can call layout()
to wrap long lines into a paragraph and justify it.
/** Creates a layout from the given attributed string.
This will replace any data that is currently stored in the layout.
*/ */
void appendText (const String& textToAppend,
const Font& fontToUse);
void createLayout (const AttributedString& text, float maxWidth);
/** Replaces all the text with a new string.
/** Creates a layout, attempting to choose a width which results in lines
of a similar length.
This is equivalent to calling clear() followed by appendText().
This will be slower than the normal createLayout method, but produces a
tidier result.
*/ */
void setText (const String& newText,
const Font& fontToUse);
void createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth);
/** Returns true if the layout has not had any text added yet. */
bool isEmpty() const;
//==============================================================================
/** Breaks the text up to form a paragraph with the given width.
@param maximumWidth any text wider than this will be split
across multiple lines
@param justification how the lines are to be laid-out horizontally
@param attemptToBalanceLineLengths if true, it will try to split the lines at a
width that keeps all the lines of text at a
similar length - this is good when you're displaying
a short message and don't want it to get split
onto two lines with only a couple of words on
the second line, which looks untidy.
/** Draws the layout within the specified area.
The position of the text within the rectangle is controlled by the justification
flags set in the original AttributedString that was used to create this layout.
*/ */
void layout (int maximumWidth,
const Justification& justification,
bool attemptToBalanceLineLengths);
void draw (Graphics& g, const Rectangle<float>& area) const;
//============================================================================== //==============================================================================
/** Returns the overall width of the entire text layout. */
int getWidth() const;
/** A positioned glyph. */
class JUCE_API Glyph
{
public:
Glyph (int glyphCode, const Point<float>& anchor, float width) noexcept;
Glyph (const Glyph&) noexcept;
Glyph& operator= (const Glyph&) noexcept;
~Glyph() noexcept;
/** The code number of this glyph. */
int glyphCode;
/** The glyph's anchor point - this is relative to the line's origin.
@see TextLayout::Line::lineOrigin
*/
Point<float> anchor;
float width;
};
/** Returns the overall height of the entire text layout. */
int getHeight() const;
//==============================================================================
/** A sequence of glyphs with a common font and colour. */
class JUCE_API Run
{
public:
Run() noexcept;
Run (const Run&);
Run (const Range<int>& stringRange, int numGlyphsToPreallocate);
~Run() noexcept;
Font font; /**< The run's font. */
Colour colour; /**< The run's colour. */
Array<Glyph> glyphs; /**< The glyphs in this run. */
Range<int> stringRange; /**< The character range that this run represents in the
original string that was used to create it. */
private:
Run& operator= (const Run&);
};
/** Returns the total number of lines of text. */
int getNumLines() const { return totalLines; }
//==============================================================================
/** A line containing a sequence of glyph-runs. */
class JUCE_API Line
{
public:
Line() noexcept;
Line (const Line&);
Line (const Range<int>& stringRange, const Point<float>& lineOrigin,
float ascent, float descent, float leading, int numRunsToPreallocate);
~Line() noexcept;
/** Returns the X position range which contains all the glyphs in this line. */
Range<float> getLineBoundsX() const noexcept;
OwnedArray<Run> runs; /**< The glyph-runs in this line. */
Range<int> stringRange; /**< The character range that this line represents in the
original string that was used to create it. */
Point<float> lineOrigin; /**< The line's baseline origin. */
float ascent, descent, leading;
private:
Line& operator= (const Line&);
};
/** Returns the width of a particular line of text.
//==============================================================================
/** Returns the maximum width of the content. */
float getWidth() const noexcept { return width; }
@param lineNumber the line, from 0 to (getNumLines() - 1)
*/
int getLineWidth (int lineNumber) const;
/** Returns the maximum height of the content. */
float getHeight() const noexcept;
//==============================================================================
/** Renders the text at a specified position using a graphics context.
*/
void draw (Graphics& g, int topLeftX, int topLeftY) const;
/** Returns the number of lines in the layout. */
int getNumLines() const noexcept { return lines.size(); }
/** Renders the text within a specified rectangle using a graphics context.
/** Returns one of the lines. */
Line& getLine (int index) const;
The justification flags dictate how the block of text should be positioned
within the rectangle.
*/
void drawWithin (Graphics& g,
int x, int y, int w, int h,
const Justification& layoutFlags) const;
/** Adds a line to the layout. The layout will take ownership of this line object
and will delete it when it is no longer needed. */
void addLine (Line* line);
/** Pre-allocates space for the specified number of lines. */
void ensureStorageAllocated (int numLinesNeeded);
private: private:
//==============================================================================
class Token;
friend class OwnedArray <Token>;
OwnedArray <Token> tokens;
int totalLines;
OwnedArray<Line> lines;
float width;
Justification justification;
JUCE_LEAK_DETECTOR (TextLayout);
void createStandardLayout (const AttributedString&);
bool createNativeLayout (const AttributedString&);
void recalculateWidth();
}; };
#endif // __JUCE_TEXTLAYOUT_JUCEHEADER__ #endif // __JUCE_TEXTLAYOUT_JUCEHEADER__

+ 1
- 1
modules/juce_graphics/juce_graphics.h View File

@@ -46,7 +46,7 @@
management and layout. management and layout.
*/ */
#ifndef JUCE_USE_DIRECTWRITE #ifndef JUCE_USE_DIRECTWRITE
#define JUCE_USE_DIRECTWRITE 0
#define JUCE_USE_DIRECTWRITE 1
#endif #endif
#ifndef JUCE_INCLUDE_PNGLIB_CODE #ifndef JUCE_INCLUDE_PNGLIB_CODE


+ 143
- 84
modules/juce_graphics/native/juce_mac_Fonts.mm View File

@@ -33,6 +33,116 @@
namespace CoreTextTypeLayout namespace CoreTextTypeLayout
{ {
CTFontRef createCTFont (const Font& font, float fontSize, bool& needsItalicTransform)
{
CFStringRef cfName = font.getTypefaceName().toCFString();
CTFontRef ctFontRef = CTFontCreateWithName (cfName, fontSize, nullptr);
CFRelease (cfName);
if (ctFontRef != nullptr)
{
if (font.isItalic())
{
CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0f, nullptr,
kCTFontItalicTrait, kCTFontItalicTrait);
if (newFont != nullptr)
{
CFRelease (ctFontRef);
ctFontRef = newFont;
}
else
{
needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform..
}
}
if (font.isBold())
{
CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0f, nullptr,
kCTFontBoldTrait, kCTFontBoldTrait);
if (newFont != nullptr)
{
CFRelease (ctFontRef);
ctFontRef = newFont;
}
}
}
return ctFontRef;
}
float getFontHeightToCGSizeFactor (const Font& font)
{
static float factor = 0;
if (factor == 0) // (This factor seems to be a constant for all fonts..)
{
bool needsItalicTransform = false;
CTFontRef tempFont = createCTFont (font, 1024, needsItalicTransform);
CGFontRef cgFontRef = CTFontCopyGraphicsFont (tempFont, nullptr);
const int totalHeight = std::abs (CGFontGetAscent (cgFontRef)) + std::abs (CGFontGetDescent (cgFontRef));
factor = CGFontGetUnitsPerEm (cgFontRef) / (float) totalHeight;
CGFontRelease (cgFontRef);
CFRelease (tempFont);
}
return factor;
}
//==============================================================================
struct Advances
{
Advances (CTRunRef run, const CFIndex numGlyphs)
: advances (CTRunGetAdvancesPtr (run))
{
if (advances == nullptr)
{
local.malloc (numGlyphs);
CTRunGetAdvances (run, CFRangeMake (0, 0), local);
advances = local;
}
}
const CGSize* advances;
HeapBlock<CGSize> local;
};
struct Glyphs
{
Glyphs (CTRunRef run, const int numGlyphs)
: glyphs (CTRunGetGlyphsPtr (run))
{
if (glyphs == nullptr)
{
local.malloc (numGlyphs);
CTRunGetGlyphs (run, CFRangeMake (0, 0), local);
glyphs = local;
}
}
const CGGlyph* glyphs;
HeapBlock<CGGlyph> local;
};
struct Positions
{
Positions (CTRunRef run, const int numGlyphs)
: points (CTRunGetPositionsPtr (run))
{
if (points == nullptr)
{
local.malloc (numGlyphs);
CTRunGetPositions (run, CFRangeMake (0, 0), local);
points = local;
}
}
const CGPoint* points;
HeapBlock<CGPoint> local;
};
//==============================================================================
CFAttributedStringRef createCFAttributedString (const AttributedString& text) CFAttributedStringRef createCFAttributedString (const AttributedString& text)
{ {
#if JUCE_IOS #if JUCE_IOS
@@ -58,17 +168,10 @@ namespace CoreTextTypeLayout
if (attr->getFont() != nullptr) if (attr->getFont() != nullptr)
{ {
// Apply fontHeightToCGSizeFactor to the font size since this is how glyphs are drawn
CTFontRef ctFontRef = CTFontCreateWithName (attr->getFont()->getTypefaceName().toCFString(), 1024, nullptr);
CGFontRef cgFontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
CFRelease (ctFontRef);
const int totalHeight = abs (CGFontGetAscent (cgFontRef)) + abs (CGFontGetDescent (cgFontRef));
float fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (cgFontRef) / (float) totalHeight;
CGFontRelease (cgFontRef);
ctFontRef = CTFontCreateWithName (attr->getFont()->getTypefaceName().toCFString(),
attr->getFont()->getHeight() * fontHeightToCGSizeFactor, nullptr);
const Font& f = *attr->getFont();
bool needsItalicTransform = false;
CTFontRef ctFontRef = createCTFont (f, f.getHeight() * getFontHeightToCGSizeFactor (f),
needsItalicTransform);
CFAttributedStringSetAttribute (attribString, CFRangeMake (range.getStart(), range.getLength()), CFAttributedStringSetAttribute (attribString, CFRangeMake (range.getStart(), range.getLength()),
kCTFontAttributeName, ctFontRef); kCTFontAttributeName, ctFontRef);
@@ -190,7 +293,7 @@ namespace CoreTextTypeLayout
CFRelease (frame); CFRelease (frame);
} }
void createLayout (GlyphLayout& glyphLayout, const AttributedString& text)
void createLayout (TextLayout& glyphLayout, const AttributedString& text)
{ {
CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text); CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString); CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString);
@@ -228,9 +331,9 @@ namespace CoreTextTypeLayout
CGFloat ascent, descent, leading; CGFloat ascent, descent, leading;
CTLineGetTypographicBounds (line, &ascent, &descent, &leading); CTLineGetTypographicBounds (line, &ascent, &descent, &leading);
GlyphLayout::Line* const glyphLine = new GlyphLayout::Line (lineStringRange, lineOrigin,
(float) ascent, (float) descent, (float) leading,
(int) numRuns);
TextLayout::Line* const glyphLine = new TextLayout::Line (lineStringRange, lineOrigin,
(float) ascent, (float) descent, (float) leading,
(int) numRuns);
glyphLayout.addLine (glyphLine); glyphLayout.addLine (glyphLine);
for (CFIndex j = 0; j < numRuns; ++j) for (CFIndex j = 0; j < numRuns; ++j)
@@ -239,10 +342,10 @@ namespace CoreTextTypeLayout
const CFIndex numGlyphs = CTRunGetGlyphCount (run); const CFIndex numGlyphs = CTRunGetGlyphCount (run);
const CFRange runStringRange = CTRunGetStringRange (run); const CFRange runStringRange = CTRunGetStringRange (run);
GlyphLayout::Run* const glyphRun = new GlyphLayout::Run (Range<int> ((int) runStringRange.location,
(int) (runStringRange.location + runStringRange.length - 1)),
(int) numGlyphs);
glyphLine->addRun (glyphRun);
TextLayout::Run* const glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
(int) (runStringRange.location + runStringRange.length - 1)),
(int) numGlyphs);
glyphLine->runs.add (glyphRun);
CFDictionaryRef runAttributes = CTRunGetAttributes (run); CFDictionaryRef runAttributes = CTRunGetAttributes (run);
@@ -250,18 +353,16 @@ namespace CoreTextTypeLayout
if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void **) &ctRunFont)) if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void **) &ctRunFont))
{ {
CFStringRef cfsFontName = CTFontCopyPostScriptName (ctRunFont); CFStringRef cfsFontName = CTFontCopyPostScriptName (ctRunFont);
const String fontName (String::fromCFString (cfsFontName));
CTFontRef ctFontRef = CTFontCreateWithName (cfsFontName, 1024, nullptr); CTFontRef ctFontRef = CTFontCreateWithName (cfsFontName, 1024, nullptr);
CFRelease (cfsFontName);
CGFontRef cgFontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); CGFontRef cgFontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
CFRelease (ctFontRef); CFRelease (ctFontRef);
const int totalHeight = std::abs (CGFontGetAscent (cgFontRef)) + std::abs (CGFontGetDescent (cgFontRef)); const int totalHeight = std::abs (CGFontGetAscent (cgFontRef)) + std::abs (CGFontGetDescent (cgFontRef));
const float fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (cgFontRef) / (float) totalHeight; const float fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (cgFontRef) / (float) totalHeight;
CGFontRelease (cgFontRef); CGFontRelease (cgFontRef);
glyphRun->setFont (Font (fontName, CTFontGetSize (ctRunFont) / fontHeightToCGSizeFactor, 0));
glyphRun->font = Font (String::fromCFString (cfsFontName),
CTFontGetSize (ctRunFont) / fontHeightToCGSizeFactor, 0); // XXX bold/italic flags?
CFRelease (cfsFontName);
} }
CGColorRef cgRunColor; CGColorRef cgRunColor;
@@ -270,28 +371,17 @@ namespace CoreTextTypeLayout
{ {
const CGFloat* const components = CGColorGetComponents (cgRunColor); const CGFloat* const components = CGColorGetComponents (cgRunColor);
glyphRun->setColour (Colour::fromFloatRGBA (components[0], components[1], components[2], components[3]));
glyphRun->colour = Colour::fromFloatRGBA (components[0], components[1], components[2], components[3]);
} }
const CGGlyph* glyphsPtr = CTRunGetGlyphsPtr (run);
const CGPoint* posPtr = CTRunGetPositionsPtr (run);
HeapBlock <CGGlyph> glyphBuffer;
HeapBlock <CGPoint> positionBuffer;
if (glyphsPtr == nullptr || posPtr == nullptr)
{
// If we can't get a direct pointer, we have to copy the metrics to get them..
glyphBuffer.malloc (numGlyphs);
glyphsPtr = glyphBuffer;
CTRunGetGlyphs (run, CFRangeMake (0, 0), glyphBuffer);
positionBuffer.malloc (numGlyphs);
posPtr = positionBuffer;
CTRunGetPositions (run, CFRangeMake (0, 0), positionBuffer);
}
const CoreTextTypeLayout::Glyphs glyphs (run, numGlyphs);
const CoreTextTypeLayout::Advances advances (run, numGlyphs);
const CoreTextTypeLayout::Positions positions (run, numGlyphs);
for (CFIndex k = 0; k < numGlyphs; ++k) for (CFIndex k = 0; k < numGlyphs; ++k)
glyphRun->addGlyph (new GlyphLayout::Glyph (glyphsPtr[k], Point<float> (posPtr[k].x, posPtr[k].y)));
glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> (positions.points[k].x,
positions.points[k].y),
advances.advances[k].width));
} }
} }
@@ -314,41 +404,11 @@ public:
ascent (0.0f), ascent (0.0f),
unitsToHeightScaleFactor (0.0f) unitsToHeightScaleFactor (0.0f)
{ {
CFStringRef cfName = font.getTypefaceName().toCFString();
ctFontRef = CTFontCreateWithName (cfName, 1024, nullptr);
CFRelease (cfName);
bool needsItalicTransform = false;
ctFontRef = CoreTextTypeLayout::createCTFont (font, 1024.0f, needsItalicTransform);
if (ctFontRef != nullptr) if (ctFontRef != nullptr)
{ {
bool needsItalicTransform = false;
if (font.isItalic())
{
CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0f, nullptr,
kCTFontItalicTrait, kCTFontItalicTrait);
if (newFont != nullptr)
{
CFRelease (ctFontRef);
ctFontRef = newFont;
}
else
{
needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform..
}
}
if (font.isBold())
{
CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0f, nullptr,
kCTFontBoldTrait, kCTFontBoldTrait);
if (newFont != nullptr)
{
CFRelease (ctFontRef);
ctFontRef = newFont;
}
}
ascent = std::abs ((float) CTFontGetAscent (ctFontRef)); ascent = std::abs ((float) CTFontGetAscent (ctFontRef));
const float totalSize = ascent + std::abs ((float) CTFontGetDescent (ctFontRef)); const float totalSize = ascent + std::abs ((float) CTFontGetDescent (ctFontRef));
ascent /= totalSize; ascent /= totalSize;
@@ -411,11 +471,11 @@ public:
{ {
CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
CFIndex length = CTRunGetGlyphCount (run); CFIndex length = CTRunGetGlyphCount (run);
HeapBlock <CGSize> advances (length);
CTRunGetAdvances (run, CFRangeMake (0, 0), advances);
const CoreTextTypeLayout::Advances advances (run, length);
for (int j = 0; j < length; ++j) for (int j = 0; j < length; ++j)
x += (float) advances[j].width;
x += (float) advances.advances[j].width;
} }
CFRelease (line); CFRelease (line);
@@ -446,16 +506,15 @@ public:
{ {
CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
CFIndex length = CTRunGetGlyphCount (run); CFIndex length = CTRunGetGlyphCount (run);
HeapBlock <CGSize> advances (length);
CTRunGetAdvances (run, CFRangeMake (0, 0), advances);
HeapBlock <CGGlyph> glyphs (length);
CTRunGetGlyphs (run, CFRangeMake (0, 0), glyphs);
const CoreTextTypeLayout::Advances advances (run, length);
const CoreTextTypeLayout::Glyphs glyphs (run, length);
for (int j = 0; j < length; ++j) for (int j = 0; j < length; ++j)
{ {
x += (float) advances[j].width;
x += (float) advances.advances[j].width;
xOffsets.add (x * unitsToHeightScaleFactor); xOffsets.add (x * unitsToHeightScaleFactor);
resultGlyphs.add (glyphs[j]);
resultGlyphs.add (glyphs.glyphs[j]);
} }
} }
@@ -1048,7 +1107,7 @@ Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
return Typeface::createSystemTypefaceFor (f); return Typeface::createSystemTypefaceFor (f);
} }
bool GlyphLayout::createNativeLayout (const AttributedString& text)
bool TextLayout::createNativeLayout (const AttributedString& text)
{ {
#if JUCE_CORETEXT_AVAILABLE #if JUCE_CORETEXT_AVAILABLE
CoreTextTypeLayout::createLayout (*this, text); CoreTextTypeLayout::createLayout (*this, text);


+ 27
- 24
modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp View File

@@ -65,7 +65,7 @@ namespace DirectWriteTypeLayout
DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription, DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription,
IUnknown* clientDrawingEffect) IUnknown* clientDrawingEffect)
{ {
GlyphLayout* const glyphLayout = static_cast<GlyphLayout*> (clientDrawingContext);
TextLayout* const layout = static_cast<TextLayout*> (clientDrawingContext);
if (baselineOriginY != lastOriginY) if (baselineOriginY != lastOriginY)
{ {
@@ -73,49 +73,52 @@ namespace DirectWriteTypeLayout
++currentLine; ++currentLine;
// The x value is only correct when dealing with LTR text // The x value is only correct when dealing with LTR text
glyphLayout->getLine (currentLine).setLineOrigin (Point<float> (baselineOriginX, baselineOriginY));
layout->getLine (currentLine).lineOrigin = Point<float> (baselineOriginX, baselineOriginY);
} }
if (currentLine < 0) if (currentLine < 0)
return S_OK; return S_OK;
GlyphLayout::Line& glyphLine = glyphLayout->getLine (currentLine);
TextLayout::Line& glyphLine = layout->getLine (currentLine);
DWRITE_FONT_METRICS dwFontMetrics; DWRITE_FONT_METRICS dwFontMetrics;
glyphRun->fontFace->GetMetrics (&dwFontMetrics); glyphRun->fontFace->GetMetrics (&dwFontMetrics);
const float ascent = scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, glyphRun);
const float descent = scaledFontSize (dwFontMetrics.descent, dwFontMetrics, glyphRun);
glyphLine.increaseAscentDescent (ascent, descent);
glyphLine.ascent = jmax (glyphLine->ascent, scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, glyphRun));
glyphLine.descent = jmax (glyphLine->descent, scaledFontSize (dwFontMetrics.descent, dwFontMetrics, glyphRun));
int styleFlags = 0; int styleFlags = 0;
const String fontName (getFontName (glyphRun, styleFlags)); const String fontName (getFontName (glyphRun, styleFlags));
GlyphLayout::Run* const glyphRunLayout = new GlyphLayout::Run (Range<int> (runDescription->textPosition,
runDescription->textPosition + runDescription->stringLength),
glyphRun->glyphCount);
glyphLine.addRun (glyphRunLayout);
TextLayout::Run* const glyphRunLayout = new TextLayout::Run (Range<int> (runDescription->textPosition,
runDescription->textPosition + runDescription->stringLength),
glyphRun->glyphCount);
glyphLine.runs.add (glyphRunLayout);
glyphRun->fontFace->GetMetrics (&dwFontMetrics); glyphRun->fontFace->GetMetrics (&dwFontMetrics);
const float totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent); const float totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent);
const float fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight; const float fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight;
glyphRunLayout->setFont (Font (fontName, glyphRun->fontEmSize / fontHeightToEmSizeFactor, styleFlags));
glyphRunLayout->setColour (getColourOf (static_cast<ID2D1SolidColorBrush*> (clientDrawingEffect)));
glyphRunLayout->font = Font (fontName, glyphRun->fontEmSize / fontHeightToEmSizeFactor, styleFlags);
glyphRunLayout->colour = getColourOf (static_cast<ID2D1SolidColorBrush*> (clientDrawingEffect));
const Point<float> lineOrigin (glyphLayout->getLine (currentLine).getLineOrigin());
const Point<float> lineOrigin (layout->getLine (currentLine).lineOrigin);
float x = baselineOriginX - lineOrigin.x; float x = baselineOriginX - lineOrigin.x;
for (UINT32 i = 0; i < glyphRun->glyphCount; ++i) for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
{ {
const float advance = glyphRun->glyphAdvances[i];
if ((glyphRun->bidiLevel & 1) != 0) if ((glyphRun->bidiLevel & 1) != 0)
x -= glyphRun->glyphAdvances[i]; // RTL text
x -= advance; // RTL text
glyphRunLayout->addGlyph (new GlyphLayout::Glyph (glyphRun->glyphIndices[i], Point<float> (x, baselineOriginY - lineOrigin.y)));
glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i],
Point<float> (x, baselineOriginY - lineOrigin.y),
advance));
if ((glyphRun->bidiLevel & 1) == 0) if ((glyphRun->bidiLevel & 1) == 0)
x += glyphRun->glyphAdvances[i]; // LTR text
x += advance; // LTR text
} }
return S_OK; return S_OK;
@@ -264,7 +267,7 @@ namespace DirectWriteTypeLayout
} }
} }
void createLayout (GlyphLayout& glyphLayout, const AttributedString& text, IDWriteFactory* const directWriteFactory,
void createLayout (TextLayout& layout, const AttributedString& text, IDWriteFactory* const directWriteFactory,
ID2D1Factory* const direct2dFactory, IDWriteFontCollection* const fontCollection) ID2D1Factory* const direct2dFactory, IDWriteFontCollection* const fontCollection)
{ {
// To add color to text, we need to create a D2D render target // To add color to text, we need to create a D2D render target
@@ -296,7 +299,7 @@ namespace DirectWriteTypeLayout
ComSmartPtr<IDWriteTextLayout> dwTextLayout; ComSmartPtr<IDWriteTextLayout> dwTextLayout;
hr = directWriteFactory->CreateTextLayout (text.getText().toWideCharPointer(), textLen, hr = directWriteFactory->CreateTextLayout (text.getText().toWideCharPointer(), textLen,
dwTextFormat, glyphLayout.getWidth(),
dwTextFormat, layout.getWidth(),
1.0e7f, dwTextLayout.resetAndGetPointerAddress()); 1.0e7f, dwTextLayout.resetAndGetPointerAddress());
const int numAttributes = text.getNumAttributes(); const int numAttributes = text.getNumAttributes();
@@ -307,7 +310,7 @@ namespace DirectWriteTypeLayout
UINT32 actualLineCount = 0; UINT32 actualLineCount = 0;
hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount); hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount);
glyphLayout.ensureStorageAllocated (actualLineCount);
layout.ensureStorageAllocated (actualLineCount);
HeapBlock <DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount); HeapBlock <DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount);
hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount); hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount);
@@ -318,19 +321,19 @@ namespace DirectWriteTypeLayout
const Range<int> lineStringRange (lastLocation, (int) lastLocation + dwLineMetrics[i].length); const Range<int> lineStringRange (lastLocation, (int) lastLocation + dwLineMetrics[i].length);
lastLocation = dwLineMetrics[i].length; lastLocation = dwLineMetrics[i].length;
GlyphLayout::Line* glyphLine = new GlyphLayout::Line();
glyphLayout.addLine (glyphLine);
glyphLine->setStringRange (lineStringRange);
TextLayout::Line* glyphLine = new TextLayout::Line();
layout.addLine (glyphLine);
glyphLine->stringRange = lineStringRange;
} }
ComSmartPtr<CustomDirectWriteTextRenderer> textRenderer (new CustomDirectWriteTextRenderer (fontCollection)); ComSmartPtr<CustomDirectWriteTextRenderer> textRenderer (new CustomDirectWriteTextRenderer (fontCollection));
hr = dwTextLayout->Draw (&glyphLayout, textRenderer, 0, 0);
hr = dwTextLayout->Draw (&layout, textRenderer, 0, 0);
} }
} }
#endif #endif
bool GlyphLayout::createNativeLayout (const AttributedString& text)
bool TextLayout::createNativeLayout (const AttributedString& text)
{ {
#if JUCE_USE_DIRECTWRITE #if JUCE_USE_DIRECTWRITE
const Direct2DFactories& factories = Direct2DFactories::getInstance(); const Direct2DFactories& factories = Direct2DFactories::getInstance();


+ 18
- 18
modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.cpp View File

@@ -80,10 +80,10 @@ namespace LookAndFeelHelpers
p.closeSubPath(); p.closeSubPath();
} }
const Colour createBaseColour (const Colour& buttonColour,
const bool hasKeyboardFocus,
const bool isMouseOverButton,
const bool isButtonDown) noexcept
Colour createBaseColour (const Colour& buttonColour,
const bool hasKeyboardFocus,
const bool isMouseOverButton,
const bool isButtonDown) noexcept
{ {
const float sat = hasKeyboardFocus ? 1.3f : 0.9f; const float sat = hasKeyboardFocus ? 1.3f : 0.9f;
const Colour baseColour (buttonColour.withMultipliedSaturation (sat)); const Colour baseColour (buttonColour.withMultipliedSaturation (sat));
@@ -96,15 +96,17 @@ namespace LookAndFeelHelpers
return baseColour; return baseColour;
} }
const TextLayout layoutTooltipText (const String& text) noexcept
TextLayout layoutTooltipText (const String& text) noexcept
{ {
const float tooltipFontSize = 12.0f;
const float tooltipFontSize = 13.0f;
const int maxToolTipWidth = 400; const int maxToolTipWidth = 400;
const Font f (tooltipFontSize, Font::bold);
TextLayout tl (text, f);
tl.layout (maxToolTipWidth, Justification::left, true);
AttributedString s;
s.setJustification (Justification::centred);
s.append (text, Font (tooltipFontSize, Font::bold));
TextLayout tl;
tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth);
return tl; return tl;
} }
} }
@@ -527,7 +529,6 @@ void LookAndFeel::drawAlertBox (Graphics& g,
g.fillAll (alert.findColour (AlertWindow::backgroundColourId)); g.fillAll (alert.findColour (AlertWindow::backgroundColourId));
int iconSpaceUsed = 0; int iconSpaceUsed = 0;
Justification alignment (Justification::horizontallyCentred);
const int iconWidth = 80; const int iconWidth = 80;
int iconSize = jmin (iconWidth + 50, alert.getHeight() + 20); int iconSize = jmin (iconWidth + 50, alert.getHeight() + 20);
@@ -577,15 +578,14 @@ void LookAndFeel::drawAlertBox (Graphics& g,
g.fillPath (icon); g.fillPath (icon);
iconSpaceUsed = iconWidth; iconSpaceUsed = iconWidth;
alignment = Justification::left;
} }
g.setColour (alert.findColour (AlertWindow::textColourId)); g.setColour (alert.findColour (AlertWindow::textColourId));
textLayout.drawWithin (g,
textArea.getX() + iconSpaceUsed, textArea.getY(),
textArea.getWidth() - iconSpaceUsed, textArea.getHeight(),
alignment.getFlags() | Justification::top);
textLayout.draw (g, Rectangle<int> (textArea.getX() + iconSpaceUsed,
textArea.getY(),
textArea.getWidth() - iconSpaceUsed,
textArea.getHeight()).toFloat());
g.setColour (alert.findColour (AlertWindow::outlineColourId)); g.setColour (alert.findColour (AlertWindow::outlineColourId));
g.drawRect (0, 0, alert.getWidth(), alert.getHeight()); g.drawRect (0, 0, alert.getWidth(), alert.getHeight());
@@ -1657,8 +1657,8 @@ void LookAndFeel::getTooltipSize (const String& tipText, int& width, int& height
{ {
const TextLayout tl (LookAndFeelHelpers::layoutTooltipText (tipText)); const TextLayout tl (LookAndFeelHelpers::layoutTooltipText (tipText));
width = tl.getWidth() + 14;
height = tl.getHeight() + 6;
width = (int) (tl.getWidth() + 14.0f);
height = (int) (tl.getHeight() + 6.0f);
} }
void LookAndFeel::drawTooltip (Graphics& g, const String& text, int width, int height) void LookAndFeel::drawTooltip (Graphics& g, const String& text, int width, int height)
@@ -1673,7 +1673,7 @@ void LookAndFeel::drawTooltip (Graphics& g, const String& text, int width, int h
const TextLayout tl (LookAndFeelHelpers::layoutTooltipText (text)); const TextLayout tl (LookAndFeelHelpers::layoutTooltipText (text));
g.setColour (findColour (TooltipWindow::textColourId)); g.setColour (findColour (TooltipWindow::textColourId));
tl.drawWithin (g, 0, 0, width, height, Justification::centred);
tl.draw (g, Rectangle<float> (0.0f, 0.0f, (float) width, (float) height));
} }
//============================================================================== //==============================================================================


+ 16
- 11
modules/juce_gui_basics/windows/juce_AlertWindow.cpp View File

@@ -117,10 +117,10 @@ void AlertWindow::setMessage (const String& message)
font = getLookAndFeel().getAlertWindowMessageFont(); font = getLookAndFeel().getAlertWindowMessageFont();
Font titleFont (font.getHeight() * 1.1f, Font::bold);
textLayout.setText (getName() + "\n\n", titleFont);
textLayout.appendText (text, font);
AttributedString newText;
newText.append (getName() + "\n\n", Font (font.getHeight() * 1.1f, Font::bold));
newText.append (text, font);
attributedText = newText;
updateLayout (true); updateLayout (true);
repaint(); repaint();
@@ -271,10 +271,13 @@ public:
void updateLayout (const int width) void updateLayout (const int width)
{ {
AttributedString s;
s.setJustification (Justification::topLeft);
s.append (getName(), getFont());
TextLayout text; TextLayout text;
text.appendText (getText(), getFont());
text.layout (width - 8, Justification::topLeft, true);
setSize (width, jmin (width, text.getHeight() + (int) getFont().getHeight()));
text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
} }
private: private:
@@ -399,18 +402,20 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize)
if (alertIconType == NoIcon) if (alertIconType == NoIcon)
{ {
textLayout.layout (w, Justification::horizontallyCentred, true);
attributedText.setJustification (Justification::centredTop);
textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
} }
else else
{ {
textLayout.layout (w, Justification::left, true);
attributedText.setJustification (Justification::topLeft);
textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
iconSpace = iconWidth; iconSpace = iconWidth;
} }
w = jmax (350, textLayout.getWidth() + iconSpace + edgeGap * 4);
w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
w = jmin (w, (int) (getParentWidth() * 0.7f)); w = jmin (w, (int) (getParentWidth() * 0.7f));
const int textLayoutH = textLayout.getHeight();
const int textLayoutH = (int) textLayout.getHeight();
const int textBottom = 16 + titleH + textLayoutH; const int textBottom = 16 + titleH + textLayoutH;
int h = textBottom; int h = textBottom;


+ 1
- 0
modules/juce_gui_basics/windows/juce_AlertWindow.h View File

@@ -446,6 +446,7 @@ protected:
private: private:
//============================================================================== //==============================================================================
String text; String text;
AttributedString attributedText;
TextLayout textLayout; TextLayout textLayout;
AlertIconType alertIconType; AlertIconType alertIconType;
ComponentBoundsConstrainer constrainer; ComponentBoundsConstrainer constrainer;


+ 14
- 13
modules/juce_gui_extra/misc/juce_BubbleMessageComponent.cpp View File

@@ -43,12 +43,8 @@ void BubbleMessageComponent::showAt (int x, int y,
const bool removeWhenMouseClicked, const bool removeWhenMouseClicked,
const bool deleteSelfAfterUse) const bool deleteSelfAfterUse)
{ {
textLayout.clear();
textLayout.setText (text, Font (14.0f));
textLayout.layout (256, Justification::centredLeft, true);
createLayout (text);
setPosition (x, y); setPosition (x, y);
init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse); init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse);
} }
@@ -58,15 +54,20 @@ void BubbleMessageComponent::showAt (Component* const component,
const bool removeWhenMouseClicked, const bool removeWhenMouseClicked,
const bool deleteSelfAfterUse) const bool deleteSelfAfterUse)
{ {
textLayout.clear();
textLayout.setText (text, Font (14.0f));
textLayout.layout (256, Justification::centredLeft, true);
createLayout (text);
setPosition (component); setPosition (component);
init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse); init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse);
} }
void BubbleMessageComponent::createLayout (const String& text)
{
AttributedString attString;
attString.append (text, Font (14.0f));
attString.setJustification (Justification::centred);
textLayout.createLayoutWithBalancedLineLengths (attString, 256);
}
void BubbleMessageComponent::init (const int numMillisecondsBeforeRemoving, void BubbleMessageComponent::init (const int numMillisecondsBeforeRemoving,
const bool removeWhenMouseClicked, const bool removeWhenMouseClicked,
const bool deleteSelfAfterUse) const bool deleteSelfAfterUse)
@@ -92,15 +93,15 @@ void BubbleMessageComponent::init (const int numMillisecondsBeforeRemoving,
void BubbleMessageComponent::getContentSize (int& w, int& h) void BubbleMessageComponent::getContentSize (int& w, int& h)
{ {
w = textLayout.getWidth() + 16;
h = textLayout.getHeight() + 16;
w = (int) (textLayout.getWidth() + 16.0f);
h = (int) (textLayout.getHeight() + 16.0f);
} }
void BubbleMessageComponent::paintContent (Graphics& g, int w, int h) void BubbleMessageComponent::paintContent (Graphics& g, int w, int h)
{ {
g.setColour (findColour (TooltipWindow::textColourId)); g.setColour (findColour (TooltipWindow::textColourId));
textLayout.drawWithin (g, 0, 0, w, h, Justification::centred);
textLayout.draw (g, Rectangle<float> (0.0f, 0.0f, (float) w, (float) h));
} }
void BubbleMessageComponent::timerCallback() void BubbleMessageComponent::timerCallback()


+ 1
- 0
modules/juce_gui_extra/misc/juce_BubbleMessageComponent.h View File

@@ -121,6 +121,7 @@ private:
int64 expiryTime; int64 expiryTime;
bool deleteAfterUse; bool deleteAfterUse;
void createLayout (const String&);
void init (int numMillisecondsBeforeRemoving, void init (int numMillisecondsBeforeRemoving,
bool removeWhenMouseClicked, bool removeWhenMouseClicked,
bool deleteSelfAfterUse); bool deleteSelfAfterUse);


Loading…
Cancel
Save