|
|
|
@@ -19,8 +19,108 @@ |
|
|
|
namespace juce
|
|
|
|
{
|
|
|
|
|
|
|
|
struct GraphicsFontHelpers
|
|
|
|
{
|
|
|
|
static auto compareFont (const Font& a, const Font& b) { return Font::compare (a, b); }
|
|
|
|
};
|
|
|
|
|
|
|
|
static auto operator< (const Font& a, const Font& b)
|
|
|
|
{
|
|
|
|
return GraphicsFontHelpers::compareFont (a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
static auto operator< (const Rectangle<T>& a, const Rectangle<T>& b)
|
|
|
|
{
|
|
|
|
const auto tie = [] (auto& t) { return std::make_tuple (t.getX(), t.getY(), t.getWidth(), t.getHeight()); };
|
|
|
|
return tie (a) < tie (b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static auto operator< (const Justification& a, const Justification& b)
|
|
|
|
{
|
|
|
|
return a.getFlags() < b.getFlags();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct ConfiguredArrangement
|
|
|
|
{
|
|
|
|
void draw (const Graphics& g) const { arrangement.draw (g, transform); }
|
|
|
|
|
|
|
|
GlyphArrangement arrangement;
|
|
|
|
AffineTransform transform;
|
|
|
|
};
|
|
|
|
|
|
|
|
template <typename ArrangementArgs>
|
|
|
|
class GlyphArrangementCache final : public DeletedAtShutdown
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
GlyphArrangementCache() = default;
|
|
|
|
|
|
|
|
~GlyphArrangementCache() override
|
|
|
|
{
|
|
|
|
clearSingletonInstance();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename ConfigureArrangement>
|
|
|
|
void draw (const Graphics& g, ArrangementArgs&& args, ConfigureArrangement&& configureArrangement)
|
|
|
|
{
|
|
|
|
const ScopedTryLock stl (lock);
|
|
|
|
|
|
|
|
if (! stl.isLocked())
|
|
|
|
{
|
|
|
|
configureArrangement (args).draw (g);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto cached = [&]
|
|
|
|
{
|
|
|
|
const auto iter = cache.find (args);
|
|
|
|
|
|
|
|
if (iter != cache.end())
|
|
|
|
{
|
|
|
|
if (iter->second.cachePosition != cacheOrder.begin())
|
|
|
|
cacheOrder.splice (cacheOrder.begin(), cacheOrder, iter->second.cachePosition);
|
|
|
|
|
|
|
|
return iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto result = cache.emplace (std::move (args), CachedGlyphArrangement { configureArrangement (args) }).first;
|
|
|
|
cacheOrder.push_front (result);
|
|
|
|
return result;
|
|
|
|
}();
|
|
|
|
|
|
|
|
cached->second.cachePosition = cacheOrder.begin();
|
|
|
|
cached->second.configured.draw (g);
|
|
|
|
|
|
|
|
while (cache.size() > cacheSize)
|
|
|
|
{
|
|
|
|
cache.erase (cacheOrder.back());
|
|
|
|
cacheOrder.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JUCE_DECLARE_SINGLETON (GlyphArrangementCache<ArrangementArgs>, false)
|
|
|
|
|
|
|
|
private:
|
|
|
|
struct CachedGlyphArrangement
|
|
|
|
{
|
|
|
|
using CachePtr = typename std::map<ArrangementArgs, CachedGlyphArrangement>::const_iterator;
|
|
|
|
ConfiguredArrangement configured;
|
|
|
|
typename std::list<CachePtr>::const_iterator cachePosition;
|
|
|
|
};
|
|
|
|
|
|
|
|
static constexpr size_t cacheSize = 128;
|
|
|
|
std::map<ArrangementArgs, CachedGlyphArrangement> cache;
|
|
|
|
std::list<typename CachedGlyphArrangement::CachePtr> cacheOrder;
|
|
|
|
CriticalSection lock;
|
|
|
|
};
|
|
|
|
|
|
|
|
template <typename ArrangementArgs>
|
|
|
|
juce::SingletonHolder<GlyphArrangementCache<ArrangementArgs>, juce::CriticalSection, false> GlyphArrangementCache<ArrangementArgs>::singletonHolder;
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
template <typename Type>
|
|
|
|
Rectangle<Type> coordsToRectangle (Type x, Type y, Type w, Type h) noexcept
|
|
|
|
{
|
|
|
|
@@ -224,67 +324,120 @@ Font Graphics::getCurrentFont() const |
|
|
|
void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY,
|
|
|
|
Justification justification) const
|
|
|
|
{
|
|
|
|
if (text.isNotEmpty())
|
|
|
|
{
|
|
|
|
// Don't pass any vertical placement flags to this method - they'll be ignored.
|
|
|
|
jassert (justification.getOnlyVerticalFlags() == 0);
|
|
|
|
if (text.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Don't pass any vertical placement flags to this method - they'll be ignored.
|
|
|
|
jassert (justification.getOnlyVerticalFlags() == 0);
|
|
|
|
|
|
|
|
auto flags = justification.getOnlyHorizontalFlags();
|
|
|
|
auto flags = justification.getOnlyHorizontalFlags();
|
|
|
|
|
|
|
|
if (flags == Justification::right && startX < context.getClipBounds().getX())
|
|
|
|
return;
|
|
|
|
if (flags == Justification::right && startX < context.getClipBounds().getX())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (flags == Justification::left && startX > context.getClipBounds().getRight())
|
|
|
|
return;
|
|
|
|
if (flags == Justification::left && startX > context.getClipBounds().getRight())
|
|
|
|
return;
|
|
|
|
|
|
|
|
GlyphArrangement arr;
|
|
|
|
arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY);
|
|
|
|
struct ArrangementArgs
|
|
|
|
{
|
|
|
|
auto tie() const noexcept { return std::tie (font, text, startX, baselineY); }
|
|
|
|
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
|
|
|
|
|
|
|
|
const Font font;
|
|
|
|
const String text;
|
|
|
|
const int startX, baselineY, flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (flags != Justification::left)
|
|
|
|
auto configureArrangement = [] (const ArrangementArgs& args)
|
|
|
|
{
|
|
|
|
AffineTransform transform;
|
|
|
|
GlyphArrangement arrangement;
|
|
|
|
arrangement.addLineOfText (args.font, args.text, (float) args.startX, (float) args.baselineY);
|
|
|
|
|
|
|
|
if (args.flags != Justification::left)
|
|
|
|
{
|
|
|
|
auto w = arr.getBoundingBox (0, -1, true).getWidth();
|
|
|
|
auto w = arrangement.getBoundingBox (0, -1, true).getWidth();
|
|
|
|
|
|
|
|
if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
|
|
|
|
if ((args.flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
|
|
|
|
w /= 2.0f;
|
|
|
|
|
|
|
|
arr.draw (*this, AffineTransform::translation (-w, 0));
|
|
|
|
transform = AffineTransform::translation (-w, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
arr.draw (*this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ConfiguredArrangement { std::move (arrangement), std::move (transform) };
|
|
|
|
};
|
|
|
|
|
|
|
|
GlyphArrangementCache<ArrangementArgs>::getInstance()->draw (*this,
|
|
|
|
{ context.getFont(), text, startX, baselineY, flags },
|
|
|
|
std::move (configureArrangement));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::drawMultiLineText (const String& text, const int startX,
|
|
|
|
const int baselineY, const int maximumLineWidth,
|
|
|
|
Justification justification, const float leading) const
|
|
|
|
{
|
|
|
|
if (text.isNotEmpty()
|
|
|
|
&& startX < context.getClipBounds().getRight())
|
|
|
|
if (text.isEmpty() || startX >= context.getClipBounds().getRight())
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct ArrangementArgs
|
|
|
|
{
|
|
|
|
GlyphArrangement arr;
|
|
|
|
arr.addJustifiedText (context.getFont(), text,
|
|
|
|
(float) startX, (float) baselineY, (float) maximumLineWidth,
|
|
|
|
justification, leading);
|
|
|
|
arr.draw (*this);
|
|
|
|
}
|
|
|
|
auto tie() const noexcept { return std::tie (font, text, startX, baselineY, maximumLineWidth, justification, leading); }
|
|
|
|
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
|
|
|
|
|
|
|
|
const Font font;
|
|
|
|
const String text;
|
|
|
|
const int startX, baselineY, maximumLineWidth;
|
|
|
|
const Justification justification;
|
|
|
|
const float leading;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto configureArrangement = [] (const ArrangementArgs& args)
|
|
|
|
{
|
|
|
|
GlyphArrangement arrangement;
|
|
|
|
arrangement.addJustifiedText (args.font, args.text,
|
|
|
|
(float) args.startX, (float) args.baselineY, (float) args.maximumLineWidth,
|
|
|
|
args.justification, args.leading);
|
|
|
|
return ConfiguredArrangement { std::move (arrangement) };
|
|
|
|
};
|
|
|
|
|
|
|
|
GlyphArrangementCache<ArrangementArgs>::getInstance()->draw (*this,
|
|
|
|
{ context.getFont(), text, startX, baselineY, maximumLineWidth, justification, leading },
|
|
|
|
std::move (configureArrangement));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::drawText (const String& text, Rectangle<float> area,
|
|
|
|
Justification justificationType, bool useEllipsesIfTooBig) const
|
|
|
|
{
|
|
|
|
if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer()))
|
|
|
|
if (text.isEmpty() || ! context.clipRegionIntersects (area.getSmallestIntegerContainer()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct ArrangementArgs
|
|
|
|
{
|
|
|
|
GlyphArrangement arr;
|
|
|
|
arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f,
|
|
|
|
area.getWidth(), useEllipsesIfTooBig);
|
|
|
|
|
|
|
|
arr.justifyGlyphs (0, arr.getNumGlyphs(),
|
|
|
|
area.getX(), area.getY(), area.getWidth(), area.getHeight(),
|
|
|
|
justificationType);
|
|
|
|
arr.draw (*this);
|
|
|
|
}
|
|
|
|
auto tie() const noexcept { return std::tie (font, text, area, justificationType, useEllipsesIfTooBig); }
|
|
|
|
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
|
|
|
|
|
|
|
|
const Font font;
|
|
|
|
const String text;
|
|
|
|
const Rectangle<float> area;
|
|
|
|
const Justification justificationType;
|
|
|
|
const bool useEllipsesIfTooBig;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto configureArrangement = [] (const ArrangementArgs& args)
|
|
|
|
{
|
|
|
|
GlyphArrangement arrangement;
|
|
|
|
arrangement.addCurtailedLineOfText (args.font, args.text, 0.0f, 0.0f,
|
|
|
|
args.area.getWidth(), args.useEllipsesIfTooBig);
|
|
|
|
|
|
|
|
arrangement.justifyGlyphs (0, arrangement.getNumGlyphs(),
|
|
|
|
args.area.getX(), args.area.getY(), args.area.getWidth(), args.area.getHeight(),
|
|
|
|
args.justificationType);
|
|
|
|
return ConfiguredArrangement { std::move (arrangement) };
|
|
|
|
};
|
|
|
|
|
|
|
|
GlyphArrangementCache<ArrangementArgs>::getInstance()->draw (*this,
|
|
|
|
{ context.getFont(), text, area, justificationType, useEllipsesIfTooBig },
|
|
|
|
std::move (configureArrangement));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::drawText (const String& text, Rectangle<int> area,
|
|
|
|
@@ -304,18 +457,37 @@ void Graphics::drawFittedText (const String& text, Rectangle<int> area, |
|
|
|
const int maximumNumberOfLines,
|
|
|
|
const float minimumHorizontalScale) const
|
|
|
|
{
|
|
|
|
if (text.isNotEmpty() && (! area.isEmpty()) && context.clipRegionIntersects (area))
|
|
|
|
if (text.isEmpty() || area.isEmpty() || ! context.clipRegionIntersects (area))
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct ArrangementArgs
|
|
|
|
{
|
|
|
|
GlyphArrangement arr;
|
|
|
|
arr.addFittedText (context.getFont(), text,
|
|
|
|
(float) area.getX(), (float) area.getY(),
|
|
|
|
(float) area.getWidth(), (float) area.getHeight(),
|
|
|
|
justification,
|
|
|
|
maximumNumberOfLines,
|
|
|
|
minimumHorizontalScale);
|
|
|
|
|
|
|
|
arr.draw (*this);
|
|
|
|
}
|
|
|
|
auto tie() const noexcept { return std::tie (font, text, area, justification, maximumNumberOfLines, minimumHorizontalScale); }
|
|
|
|
bool operator< (const ArrangementArgs& other) const noexcept { return tie() < other.tie(); }
|
|
|
|
|
|
|
|
const Font font;
|
|
|
|
const String text;
|
|
|
|
const Rectangle<float> area;
|
|
|
|
const Justification justification;
|
|
|
|
const int maximumNumberOfLines;
|
|
|
|
const float minimumHorizontalScale;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto configureArrangement = [] (const ArrangementArgs& args)
|
|
|
|
{
|
|
|
|
GlyphArrangement arrangement;
|
|
|
|
arrangement.addFittedText (args.font, args.text,
|
|
|
|
args.area.getX(), args.area.getY(),
|
|
|
|
args.area.getWidth(), args.area.getHeight(),
|
|
|
|
args.justification,
|
|
|
|
args.maximumNumberOfLines,
|
|
|
|
args.minimumHorizontalScale);
|
|
|
|
return ConfiguredArrangement { std::move (arrangement) };
|
|
|
|
};
|
|
|
|
|
|
|
|
GlyphArrangementCache<ArrangementArgs>::getInstance()->draw (*this,
|
|
|
|
{ context.getFont(), text, area.toFloat(), justification, maximumNumberOfLines, minimumHorizontalScale },
|
|
|
|
std::move (configureArrangement));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::drawFittedText (const String& text, int x, int y, int width, int height,
|
|
|
|
|