1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Graphics: Added a global GlyphArrangement cache

This commit is contained in:
Tom Poole 2022-03-15 11:41:24 +00:00
parent f6995ea217
commit 68514d626c
3 changed files with 245 additions and 58 deletions

View file

@ -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())
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();
if (flags == Justification::right && startX < context.getClipBounds().getX())
return;
if (flags == Justification::left && startX > context.getClipBounds().getRight())
return;
struct ArrangementArgs
{
// Don't pass any vertical placement flags to this method - they'll be ignored.
jassert (justification.getOnlyVerticalFlags() == 0);
auto tie() const noexcept { return std::tie (font, text, startX, baselineY); }
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
auto flags = justification.getOnlyHorizontalFlags();
const Font font;
const String text;
const int startX, baselineY, flags;
};
if (flags == Justification::right && startX < context.getClipBounds().getX())
return;
auto configureArrangement = [] (const ArrangementArgs& args)
{
AffineTransform transform;
GlyphArrangement arrangement;
arrangement.addLineOfText (args.font, args.text, (float) args.startX, (float) args.baselineY);
if (flags == Justification::left && startX > context.getClipBounds().getRight())
return;
GlyphArrangement arr;
arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY);
if (flags != Justification::left)
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()))
{
GlyphArrangement arr;
arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f,
area.getWidth(), useEllipsesIfTooBig);
if (text.isEmpty() || ! context.clipRegionIntersects (area.getSmallestIntegerContainer()))
return;
arr.justifyGlyphs (0, arr.getNumGlyphs(),
area.getX(), area.getY(), area.getWidth(), area.getHeight(),
justificationType);
arr.draw (*this);
}
struct ArrangementArgs
{
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))
{
GlyphArrangement arr;
arr.addFittedText (context.getFont(), text,
(float) area.getX(), (float) area.getY(),
(float) area.getWidth(), (float) area.getHeight(),
justification,
maximumNumberOfLines,
minimumHorizontalScale);
if (text.isEmpty() || area.isEmpty() || ! context.clipRegionIntersects (area))
return;
arr.draw (*this);
}
struct ArrangementArgs
{
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,