First Commit

This commit is contained in:
Legaeli
2025-10-22 09:20:23 +02:00
commit a36f2ca925
4393 changed files with 1570219 additions and 0 deletions
@@ -0,0 +1,136 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace detail
{
using ColorSpacePtr = CFUniquePtr<CGColorSpaceRef>;
using ContextPtr = CFUniquePtr<CGContextRef>;
using DataProviderPtr = CFUniquePtr<CGDataProviderRef>;
using ImagePtr = CFUniquePtr<CGImageRef>;
using GradientPtr = CFUniquePtr<CGGradientRef>;
using ColorPtr = CFUniquePtr<CGColorRef>;
using PathPtr = CFUniquePtr<CGPathRef>;
using MutablePathPtr = CFUniquePtr<CGMutablePathRef>;
}
//==============================================================================
class CoreGraphicsContext : public LowLevelGraphicsContext
{
public:
CoreGraphicsContext (CGContextRef context, float flipHeight);
~CoreGraphicsContext() override;
//==============================================================================
bool isVectorDevice() const override { return false; }
void setOrigin (Point<int>) override;
void addTransform (const AffineTransform&) override;
float getPhysicalPixelScaleFactor() const override;
bool clipToRectangle (const Rectangle<int>&) override;
bool clipToRectangleList (const RectangleList<int>&) override;
void excludeClipRectangle (const Rectangle<int>&) override;
void clipToPath (const Path&, const AffineTransform&) override;
void clipToImageAlpha (const Image&, const AffineTransform&) override;
bool clipRegionIntersects (const Rectangle<int>&) override;
Rectangle<int> getClipBounds() const override;
bool isClipEmpty() const override;
//==============================================================================
void saveState() override;
void restoreState() override;
void beginTransparencyLayer (float opacity) override;
void endTransparencyLayer() override;
//==============================================================================
void setFill (const FillType&) override;
void setOpacity (float) override;
void setInterpolationQuality (Graphics::ResamplingQuality) override;
//==============================================================================
void fillAll() override;
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override;
void fillRect (const Rectangle<float>&) override;
void fillRectList (const RectangleList<float>&) override;
void fillPath (const Path&, const AffineTransform&) override;
void strokePath (const Path& path, const PathStrokeType& strokeType, const AffineTransform& transform) override;
void drawImage (const Image& sourceImage, const AffineTransform&) override;
//==============================================================================
void drawLine (const Line<float>&) override;
void setFont (const Font&) override;
const Font& getFont() override;
void drawGlyphs (Span<const uint16_t>,
Span<const Point<float>>,
const AffineTransform&) override;
uint64_t getFrameId() const override { return 0; }
void drawEllipse (const Rectangle<float>& area, float lineThickness) override;
void fillEllipse (const Rectangle<float>& area) override;
void drawRoundedRectangle (const Rectangle<float>& r, float cornerSize, float lineThickness) override;
void fillRoundedRectangle (const Rectangle<float>& r, float cornerSize) override;
void drawLineWithThickness (const Line<float>& line, float lineThickness) override;
private:
//==============================================================================
detail::ContextPtr context;
const CGFloat flipHeight;
detail::ColorSpacePtr rgbColourSpace, greyColourSpace;
mutable std::optional<Rectangle<int>> lastClipRect;
struct SavedState;
std::unique_ptr<SavedState> state;
OwnedArray<SavedState> stateStack;
template <class RectType>
CGRect convertToCGRectFlipped (RectType r) const noexcept;
void setContextClipToCurrentPath (bool useNonZeroWinding);
void drawCurrentPath (CGPathDrawingMode mode);
void drawGradient();
void createPath (const Path&, const AffineTransform&) const;
void flip() const;
void applyTransform (const AffineTransform&) const;
void drawImage (const Image&, const AffineTransform&, bool fillEntireClipAsTiles);
bool clipToRectangleListWithoutTest (const RectangleList<float>&);
void fillCGRect (const CGRect&, bool replaceExistingContents);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsContext)
};
} // namespace juce
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,117 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
namespace
{
template <class RectType>
Rectangle<int> convertToRectInt (RectType r) noexcept
{
return { (int) r.origin.x,
(int) r.origin.y,
(int) r.size.width,
(int) r.size.height };
}
template <class RectType>
Rectangle<float> convertToRectFloat (RectType r) noexcept
{
return { (float) r.origin.x,
(float) r.origin.y,
(float) r.size.width,
(float) r.size.height };
}
template <class RectType>
CGRect convertToCGRect (RectType r) noexcept
{
return CGRectMake ((CGFloat) r.getX(), (CGFloat) r.getY(), (CGFloat) r.getWidth(), (CGFloat) r.getHeight());
}
template <class PointType>
Point<float> convertToPointFloat (PointType p) noexcept
{
return { (float) p.x, (float) p.y };
}
template <typename PointType>
CGPoint convertToCGPoint (PointType p) noexcept
{
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y);
}
template <class PointType>
Point<int> roundToIntPoint (PointType p) noexcept
{
return { roundToInt (p.x), roundToInt (p.y) };
}
#if JUCE_MAC
inline CGFloat getMainScreenHeight() noexcept
{
if ([[NSScreen screens] count] == 0)
return 0.0f;
return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
}
inline NSRect flippedScreenRect (NSRect r) noexcept
{
r.origin.y = getMainScreenHeight() - (r.origin.y + r.size.height);
return r;
}
inline NSPoint flippedScreenPoint (NSPoint p) noexcept
{
p.y = getMainScreenHeight() - p.y;
return p;
}
#endif
}
CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef);
CGContextRef juce_getImageContext (const Image&);
#if JUCE_IOS
Image juce_createImageFromUIImage (UIImage*);
#endif
#if JUCE_MAC
NSImage* imageToNSImage (const ScaledImage& image);
#endif
} // namespace juce
@@ -0,0 +1,186 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
class Direct2DGraphicsContext : public LowLevelGraphicsContext
{
public:
Direct2DGraphicsContext();
~Direct2DGraphicsContext() override;
//==============================================================================
bool isVectorDevice() const override { return false; }
void setOrigin (Point<int>) override;
void addTransform (const AffineTransform&) override;
float getPhysicalPixelScaleFactor() const override;
bool clipToRectangle (const Rectangle<int>&) override;
bool clipToRectangleList (const RectangleList<int>&) override;
void excludeClipRectangle (const Rectangle<int>&) override;
void clipToPath (const Path&, const AffineTransform&) override;
void clipToImageAlpha (const Image&, const AffineTransform&) override;
bool clipRegionIntersects (const Rectangle<int>&) override;
Rectangle<int> getClipBounds() const override;
bool isClipEmpty() const override;
//==============================================================================
void saveState() override;
void restoreState() override;
void beginTransparencyLayer (float opacity) override;
void endTransparencyLayer() override;
//==============================================================================
void setFill (const FillType&) override;
void setOpacity (float) override;
void setInterpolationQuality (Graphics::ResamplingQuality) override;
//==============================================================================
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override;
void fillRect (const Rectangle<float>&) override;
void fillRectList (const RectangleList<float>&) override;
void fillPath (const Path&, const AffineTransform&) override;
void drawImage (const Image& sourceImage, const AffineTransform&) override;
//==============================================================================
void drawLine (const Line<float>&) override;
void setFont (const Font&) override;
const Font& getFont() override;
void drawGlyphs (Span<const uint16_t>,
Span<const Point<float>>,
const AffineTransform&) override;
//==============================================================================
// These methods were not originally part of the LowLevelGraphicsContext; they
// were added because Direct2D supports these drawing primitives directly.
// The specialised functions are more efficient than emulating the same behaviour, e.g.
// by drawing paths.
void drawLineWithThickness (const Line<float>&, float) override;
void drawEllipse (const Rectangle<float>& area, float lineThickness) override;
void fillEllipse (const Rectangle<float>& area) override;
void drawRect (const Rectangle<float>&, float) override;
void strokePath (const Path&, const PathStrokeType& strokeType, const AffineTransform&) override;
void drawRoundedRectangle (const Rectangle<float>& area, float cornerSize, float lineThickness) override;
void fillRoundedRectangle (const Rectangle<float>& area, float cornerSize) override;
//==============================================================================
bool startFrame (float dpiScale);
void endFrame();
virtual Image createSnapshot() const { return {}; }
uint64_t getFrameId() const override { return frame; }
Direct2DMetrics::Ptr metrics;
protected:
struct SavedState;
SavedState* currentState = nullptr;
class PendingClipList
{
public:
void clipTo (Rectangle<float> i)
{
if (std::exchange (clipApplied, true))
list.clipTo (i);
else
list = i;
}
template <typename Numeric>
void clipTo (const RectangleList<Numeric>& other)
{
if (std::exchange (clipApplied, true))
{
list.clipTo (other);
}
else
{
list.clear();
for (const auto& r : other)
list.add (r.toFloat());
}
}
void subtract (Rectangle<float> i)
{
list.subtract (i);
clipApplied = true;
}
RectangleList<float> getList() const { return list; }
bool isClipApplied() const { return clipApplied; }
void reset (Rectangle<float> maxBounds)
{
list = maxBounds;
clipApplied = false;
}
private:
RectangleList<float> list;
bool clipApplied = false;
};
PendingClipList pendingClipList;
struct Pimpl;
virtual Pimpl* getPimpl() const noexcept = 0;
void resetPendingClipList();
void applyPendingClipList();
virtual void clearTargetBuffer() = 0;
struct ScopedTransform
{
ScopedTransform (Pimpl&, SavedState*);
ScopedTransform (Pimpl&, SavedState*, const AffineTransform& transform);
~ScopedTransform();
Pimpl& pimpl;
SavedState* state = nullptr;
};
uint64_t frame = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DGraphicsContext)
};
} // namespace juce
@@ -0,0 +1,364 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
class ScopedMultithread
{
public:
explicit ScopedMultithread (ID2D1Multithread* multithreadIn)
: multithread (addComSmartPtrOwner (multithreadIn))
{
multithreadIn->Enter();
}
~ScopedMultithread()
{
multithread->Leave();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedMultithread)
ComSmartPtr<ID2D1Multithread> multithread;
};
/* ScopedGeometryWithSink creates an ID2D1PathGeometry object with an open sink. */
struct ScopedGeometryWithSink
{
ScopedGeometryWithSink (ID2D1Factory* factory, D2D1_FILL_MODE fillMode)
{
if (const auto hr = factory->CreatePathGeometry (geometry.resetAndGetPointerAddress()); FAILED (hr))
return;
if (const auto hr = geometry->Open (sink.resetAndGetPointerAddress()); FAILED (hr))
return;
sink->SetFillMode (fillMode);
}
~ScopedGeometryWithSink()
{
if (sink == nullptr)
return;
const auto hr = sink->Close();
jassertquiet (SUCCEEDED (hr));
}
ComSmartPtr<ID2D1PathGeometry> geometry;
ComSmartPtr<ID2D1GeometrySink> sink;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedGeometryWithSink)
};
class WindowsScopedEvent
{
public:
explicit WindowsScopedEvent (HANDLE handleIn)
: handle (handleIn)
{
}
WindowsScopedEvent()
: WindowsScopedEvent (CreateEvent (nullptr, FALSE, FALSE, nullptr))
{
}
HANDLE getHandle() const noexcept
{
return handle.get();
}
private:
std::unique_ptr<std::remove_pointer_t<HANDLE>, FunctionPointerDestructor<CloseHandle>> handle;
};
//==============================================================================
struct D2DHelpers
{
static bool isTransformAxisAligned (const AffineTransform& transform)
{
return transform.mat01 == 0.0f && transform.mat10 == 0.0f;
}
static void pathToGeometrySink (const Path& path,
ID2D1GeometrySink* sink,
const AffineTransform& transform,
D2D1_FIGURE_BEGIN figureMode)
{
class ScopedFigure
{
public:
ScopedFigure (ID2D1GeometrySink* s, D2D1_POINT_2F pt, D2D1_FIGURE_BEGIN mode)
: sink (s)
{
sink->BeginFigure (pt, mode);
}
~ScopedFigure()
{
if (sink != nullptr)
sink->EndFigure (end);
}
void setClosed()
{
end = D2D1_FIGURE_END_CLOSED;
}
private:
ID2D1GeometrySink* sink = nullptr;
D2D1_FIGURE_END end = D2D1_FIGURE_END_OPEN;
JUCE_DECLARE_NON_COPYABLE (ScopedFigure)
JUCE_DECLARE_NON_MOVEABLE (ScopedFigure)
};
// Every call to BeginFigure must have a matching call to EndFigure. But - the Path does not necessarily
// have matching startNewSubPath and closePath markers.
D2D1_POINT_2F lastLocation{};
std::optional<ScopedFigure> figure;
Path::Iterator it (path);
const auto doTransform = [&transform] (float x, float y)
{
transform.transformPoint (x, y);
return D2D1_POINT_2F { x, y };
};
const auto updateFigure = [&] (float x, float y)
{
if (! figure.has_value())
figure.emplace (sink, lastLocation, figureMode);
lastLocation = doTransform (x, y);
};
while (it.next())
{
switch (it.elementType)
{
case Path::Iterator::lineTo:
updateFigure (it.x1, it.y1);
sink->AddLine (lastLocation);
break;
case Path::Iterator::quadraticTo:
updateFigure (it.x2, it.y2);
sink->AddQuadraticBezier ({ doTransform (it.x1, it.y1), lastLocation });
break;
case Path::Iterator::cubicTo:
updateFigure (it.x3, it.y3);
sink->AddBezier ({ doTransform (it.x1, it.y1), doTransform (it.x2, it.y2), lastLocation });
break;
case Path::Iterator::closePath:
if (figure.has_value())
figure->setClosed();
figure.reset();
break;
case Path::Iterator::startNewSubPath:
figure.reset();
lastLocation = doTransform (it.x1, it.y1);
figure.emplace (sink, lastLocation, figureMode);
break;
}
}
}
static D2D1_POINT_2F pointTransformed (Point<float> pt, const AffineTransform& transform)
{
transform.transformPoint (pt.x, pt.y);
return { (FLOAT) pt.x, (FLOAT) pt.y };
}
static void rectToGeometrySink (const Rectangle<float>& rect,
ID2D1GeometrySink* sink,
const AffineTransform& transform,
D2D1_FIGURE_BEGIN figureMode)
{
const auto a = pointTransformed (rect.getTopLeft(), transform);
const auto b = pointTransformed (rect.getTopRight(), transform);
const auto c = pointTransformed (rect.getBottomRight(), transform);
const auto d = pointTransformed (rect.getBottomLeft(), transform);
sink->BeginFigure (a, figureMode);
sink->AddLine (b);
sink->AddLine (c);
sink->AddLine (d);
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
}
static ComSmartPtr<ID2D1Geometry> rectListToPathGeometry (ID2D1Factory* factory,
const RectangleList<float>& clipRegion,
const AffineTransform& transform,
D2D1_FILL_MODE fillMode,
D2D1_FIGURE_BEGIN figureMode,
[[maybe_unused]] Direct2DMetrics* metrics)
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createGeometryTime)
ScopedGeometryWithSink objects { factory, fillMode };
if (objects.sink == nullptr)
return {};
for (int i = clipRegion.getNumRectangles(); --i >= 0;)
rectToGeometrySink (clipRegion.getRectangle (i), objects.sink, transform, figureMode);
return objects.geometry;
}
static ComSmartPtr<ID2D1Geometry> pathToPathGeometry (ID2D1Factory* factory,
const Path& path,
const AffineTransform& transform,
D2D1_FIGURE_BEGIN figureMode,
[[maybe_unused]] Direct2DMetrics* metrics)
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createGeometryTime)
ScopedGeometryWithSink objects { factory, path.isUsingNonZeroWinding() ? D2D1_FILL_MODE_WINDING : D2D1_FILL_MODE_ALTERNATE };
if (objects.sink == nullptr)
return {};
pathToGeometrySink (path, objects.sink, transform, figureMode);
return objects.geometry;
}
static ComSmartPtr<ID2D1StrokeStyle1> pathStrokeTypeToStrokeStyle (ID2D1Factory1* factory, const PathStrokeType& strokeType)
{
// JUCE JointStyle ID2D1StrokeStyle
// --------------- ----------------
// mitered D2D1_LINE_JOIN_MITER
// curved D2D1_LINE_JOIN_ROUND
// beveled D2D1_LINE_JOIN_BEVEL
//
// JUCE EndCapStyle ID2D1StrokeStyle
// ---------------- ----------------
// butt D2D1_CAP_STYLE_FLAT
// square D2D1_CAP_STYLE_SQUARE
// rounded D2D1_CAP_STYLE_ROUND
auto lineJoin = D2D1_LINE_JOIN_MITER;
switch (strokeType.getJointStyle())
{
case PathStrokeType::JointStyle::mitered:
// already set
break;
case PathStrokeType::JointStyle::curved:
lineJoin = D2D1_LINE_JOIN_ROUND;
break;
case PathStrokeType::JointStyle::beveled:
lineJoin = D2D1_LINE_JOIN_BEVEL;
break;
default:
// invalid EndCapStyle
jassertfalse;
break;
}
auto capStyle = D2D1_CAP_STYLE_FLAT;
switch (strokeType.getEndStyle())
{
case PathStrokeType::EndCapStyle::butt:
// already set
break;
case PathStrokeType::EndCapStyle::square:
capStyle = D2D1_CAP_STYLE_SQUARE;
break;
case PathStrokeType::EndCapStyle::rounded:
capStyle = D2D1_CAP_STYLE_ROUND;
break;
default:
// invalid EndCapStyle
jassertfalse;
break;
}
D2D1_STROKE_STYLE_PROPERTIES1 strokeStyleProperties
{
capStyle,
capStyle,
capStyle,
lineJoin,
strokeType.getStrokeThickness(),
D2D1_DASH_STYLE_SOLID,
0.0f,
D2D1_STROKE_TRANSFORM_TYPE_NORMAL
};
ComSmartPtr<ID2D1StrokeStyle1> strokeStyle;
factory->CreateStrokeStyle (strokeStyleProperties,
nullptr,
0,
strokeStyle.resetAndGetPointerAddress());
return strokeStyle;
}
};
//==============================================================================
/** Heap storage for a DirectWrite glyph run */
class DirectWriteGlyphRun
{
public:
void replace (Span<const Point<float>> positions, float scale)
{
advances.resize (positions.size(), 0.0f);
offsets.resize (positions.size());
std::transform (positions.begin(), positions.end(), offsets.begin(), [&] (auto& g)
{
return DWRITE_GLYPH_OFFSET { g.x / scale, -g.y };
});
}
auto* getAdvances() const { return advances.data(); }
auto* getOffsets() const { return offsets .data(); }
private:
std::vector<float> advances;
std::vector<DWRITE_GLYPH_OFFSET> offsets;
};
} // namespace juce
@@ -0,0 +1,423 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct Direct2DHwndContext::HwndPimpl : public Direct2DGraphicsContext::Pimpl
{
private:
struct SwapChainThread : private AsyncUpdater
{
SwapChainThread (Direct2DHwndContext::HwndPimpl& ownerIn, HANDLE swapHandle)
: owner (ownerIn),
swapChainEventHandle (swapHandle)
{
}
~SwapChainThread() override
{
cancelPendingUpdate();
SetEvent (quitEvent.getHandle());
thread.join();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SwapChainThread)
private:
Direct2DHwndContext::HwndPimpl& owner;
HANDLE swapChainEventHandle = nullptr;
WindowsScopedEvent quitEvent;
std::thread thread { [&] { threadLoop(); } };
void handleAsyncUpdate() override
{
owner.swapEventReceived = true;
owner.present();
}
void threadLoop()
{
Thread::setCurrentThreadName ("JUCE D2D swap chain thread");
for (;;)
{
const HANDLE handles[] { swapChainEventHandle, quitEvent.getHandle() };
const auto waitResult = WaitForMultipleObjects ((DWORD) std::size (handles), handles, FALSE, INFINITE);
switch (waitResult)
{
case WAIT_OBJECT_0:
{
triggerAsyncUpdate();
break;
}
case WAIT_OBJECT_0 + 1:
return;
case WAIT_FAILED:
default:
jassertfalse;
break;
}
}
}
};
SwapChain swap;
ComSmartPtr<ID2D1DeviceContext1> deviceContext;
std::unique_ptr<SwapChainThread> swapChainThread;
std::optional<CompositionTree> compositionTree;
// Areas that must be repainted during the next paint call, between startFrame/endFrame
RectangleList<int> deferredRepaints;
// Areas that have been updated in the backbuffer, but not presented
RectangleList<int> dirtyRegionsInBackBuffer;
std::vector<RECT> dirtyRectangles;
int64 lastFinishFrameTicks = 0;
HWND hwnd = nullptr;
// Set to true after the swap event is signalled, indicating that we're allowed to try presenting
// a new frame.
bool swapEventReceived = false;
bool prepare() override
{
const auto adapter = directX->adapters.getAdapterForHwnd (hwnd);
if (adapter == nullptr)
return false;
if (deviceContext == nullptr)
deviceContext = Direct2DDeviceContext::create (adapter);
if (deviceContext == nullptr)
return false;
if (! deviceResources.has_value())
deviceResources = Direct2DDeviceResources::create (deviceContext);
if (! deviceResources.has_value())
return false;
if (! hwnd || getClientRect().isEmpty())
return false;
if (! swap.canPaint())
{
if (auto hr = swap.create (hwnd, getClientRect(), adapter); FAILED (hr))
return false;
}
if (swapChainThread == nullptr)
if (auto* e = swap.getEvent())
swapChainThread = std::make_unique<SwapChainThread> (*this, e->getHandle());
if (! compositionTree.has_value())
compositionTree = CompositionTree::create (adapter->dxgiDevice, hwnd, swap.getChain());
if (! compositionTree.has_value())
return false;
return true;
}
void teardown() override
{
compositionTree.reset();
swapChainThread = nullptr;
deviceContext = nullptr;
swap = {};
Pimpl::teardown();
}
RectangleList<int> getPaintAreas() const override
{
return deferredRepaints;
}
bool checkPaintReady() override
{
// Try not to saturate the message thread; this is a little crude. Perhaps some kind of credit system...
if (auto now = Time::getHighResolutionTicks(); Time::highResolutionTicksToSeconds (now - lastFinishFrameTicks) < 0.001)
return false;
bool ready = Pimpl::checkPaintReady();
ready &= swap.canPaint();
ready &= compositionTree.has_value();
return ready;
}
JUCE_DECLARE_WEAK_REFERENCEABLE (HwndPimpl)
public:
HwndPimpl (Direct2DHwndContext& ownerIn, HWND hwndIn)
: Pimpl (ownerIn),
hwnd (hwndIn)
{
}
~HwndPimpl() override = default;
void handleShowWindow()
{
// One of the trickier problems was determining when Direct2D & DXGI resources can be safely created;
// that's not really spelled out in the documentation.
// This method is called when the component peer receives WM_SHOWWINDOW
prepare();
deferredRepaints = getClientRect();
}
Rectangle<int> getClientRect() const
{
RECT clientRect;
GetClientRect (hwnd, &clientRect);
return Rectangle<int>::leftTopRightBottom (clientRect.left, clientRect.top, clientRect.right, clientRect.bottom);
}
Rectangle<int> getFrameSize() const override
{
return getClientRect();
}
ComSmartPtr<ID2D1DeviceContext1> getDeviceContext() const override
{
return deviceContext;
}
ComSmartPtr<ID2D1Image> getDeviceContextTarget() const override
{
return swap.getBuffer();
}
void setSize (Rectangle<int> size)
{
if (size == swap.getSize() || size.isEmpty())
return;
// Require the entire window to be repainted
deferredRepaints = size;
// The backbuffer has no valid content until we paint a full frame
dirtyRegionsInBackBuffer.clear();
InvalidateRect (hwnd, nullptr, TRUE);
// Resize/scale the swap chain
prepare();
auto hr = swap.resize (size);
jassert (SUCCEEDED (hr));
if (FAILED (hr))
teardown();
}
void addDeferredRepaint (Rectangle<int> deferredRepaint)
{
deferredRepaints.add (deferredRepaint);
JUCE_TRACE_EVENT_INT_RECT (etw::repaint, etw::paintKeyword, snappedRectangle);
}
SavedState* startFrame() override
{
setSize (getClientRect());
auto* savedState = Pimpl::startFrame();
if (savedState == nullptr)
return nullptr;
// If a new frame is starting, clear deferredAreas in case repaint is called
// while the frame is being painted to ensure the new areas are painted on the
// next frame
dirtyRegionsInBackBuffer.add (deferredRepaints);
deferredRepaints.clear();
JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintStart, owner.getFrameId());
return savedState;
}
HRESULT finishFrame() override
{
const auto result = Pimpl::finishFrame();
present();
lastFinishFrameTicks = Time::getHighResolutionTicks();
return result;
}
void present()
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, present1Duration);
if (swap.getBuffer() == nullptr || dirtyRegionsInBackBuffer.isEmpty() || ! swapEventReceived)
return;
auto const swapChainSize = swap.getSize();
DXGI_PRESENT_PARAMETERS presentParameters{};
if (! dirtyRegionsInBackBuffer.containsRectangle (swapChainSize))
{
// Allocate enough memory for the array of dirty rectangles
dirtyRectangles.resize ((size_t) dirtyRegionsInBackBuffer.getNumRectangles());
// Fill the array of dirty rectangles, intersecting each paint area with the swap chain buffer
presentParameters.pDirtyRects = dirtyRectangles.data();
presentParameters.DirtyRectsCount = 0;
for (const auto& area : dirtyRegionsInBackBuffer)
{
if (const auto intersection = area.getIntersection (swapChainSize); ! intersection.isEmpty())
presentParameters.pDirtyRects[presentParameters.DirtyRectsCount++] = D2DUtilities::toRECT (intersection);
}
}
// Present the freshly painted buffer
const auto hr = swap.getChain()->Present1 (swap.presentSyncInterval, swap.presentFlags, &presentParameters);
jassertquiet (SUCCEEDED (hr));
if (FAILED (hr))
return;
// We managed to present a frame, so we should avoid rendering anything or calling
// present again until that frame has been shown on-screen.
swapEventReceived = false;
// There's nothing waiting to be displayed in the backbuffer.
dirtyRegionsInBackBuffer.clear();
JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintEnd, owner.getFrameId());
}
Image createSnapshot() const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
// This won't capture child windows. Perhaps a better approach would be to use
// IGraphicsCaptureItemInterop, although this is only supported on Windows 10 v1903+
if (deviceContext == nullptr)
return {};
const auto buffer = swap.getBuffer();
if (buffer == nullptr)
return {};
// Create the bitmap to receive the snapshot
D2D1_BITMAP_PROPERTIES1 bitmapProperties{};
bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
bitmapProperties.pixelFormat = buffer->GetPixelFormat();
const auto swapRect = swap.getSize();
const auto size = D2D1::SizeU ((UINT32) swapRect.getWidth(), (UINT32) swapRect.getHeight());
ComSmartPtr<ID2D1Bitmap1> snapshot;
if (const auto hr = deviceContext->CreateBitmap (size, nullptr, 0, bitmapProperties, snapshot.resetAndGetPointerAddress()); FAILED (hr))
return {};
swap.getChain()->Present (0, DXGI_PRESENT_DO_NOT_WAIT);
// Copy the swap chain buffer to the bitmap snapshot
D2D_POINT_2U p { 0, 0 };
const auto sourceRect = D2DUtilities::toRECT_U (swapRect);
if (const auto hr = snapshot->CopyFromBitmap (&p, buffer, &sourceRect); FAILED (hr))
return {};
const Image result { new Direct2DPixelData { D2DUtilities::getDeviceForContext (deviceContext), snapshot } };
swap.getChain()->Present (0, DXGI_PRESENT_DO_NOT_WAIT);
return result;
}
};
//==============================================================================
Direct2DHwndContext::Direct2DHwndContext (HWND windowHandle)
{
#if JUCE_DIRECT2D_METRICS
metrics = new Direct2DMetrics { Direct2DMetricsHub::getInstance()->lock,
"HWND " + String::toHexString ((pointer_sized_int) windowHandle),
windowHandle };
Direct2DMetricsHub::getInstance()->add (metrics);
#endif
pimpl = std::make_unique<HwndPimpl> (*this, windowHandle);
}
Direct2DHwndContext::~Direct2DHwndContext()
{
#if JUCE_DIRECT2D_METRICS
Direct2DMetricsHub::getInstance()->remove (metrics);
#endif
}
Direct2DGraphicsContext::Pimpl* Direct2DHwndContext::getPimpl() const noexcept
{
return pimpl.get();
}
void Direct2DHwndContext::handleShowWindow()
{
pimpl->handleShowWindow();
}
void Direct2DHwndContext::addDeferredRepaint (Rectangle<int> deferredRepaint)
{
pimpl->addDeferredRepaint (deferredRepaint);
}
Image Direct2DHwndContext::createSnapshot() const
{
return pimpl->createSnapshot();
}
void Direct2DHwndContext::clearTargetBuffer()
{
applyPendingClipList();
pimpl->getDeviceContext()->Clear();
}
} // namespace juce
@@ -0,0 +1,60 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
class Direct2DHwndContext : public Direct2DGraphicsContext
{
public:
explicit Direct2DHwndContext (HWND windowHandle);
~Direct2DHwndContext() override;
void handleShowWindow();
void addDeferredRepaint (Rectangle<int> deferredRepaint);
Image createSnapshot() const override;
private:
struct HwndPimpl;
std::unique_ptr<HwndPimpl> pimpl;
Pimpl* getPimpl() const noexcept override;
void clearTargetBuffer() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DHwndContext)
};
} // namespace juce
@@ -0,0 +1,112 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct Direct2DImageContext::ImagePimpl : public Direct2DGraphicsContext::Pimpl
{
public:
ImagePimpl (Direct2DImageContext& ownerIn,
ComSmartPtr<ID2D1DeviceContext1> contextIn,
ComSmartPtr<ID2D1Bitmap1> bitmapIn,
const RectangleList<int>& paintAreasIn)
: Pimpl (ownerIn),
context (std::move (contextIn)),
bitmap (std::move (bitmapIn)),
paintAreas (paintAreasIn)
{
}
Rectangle<int> getFrameSize() const override
{
if (bitmap == nullptr)
return {};
const auto size = bitmap->GetSize();
return { (int) size.width, (int) size.height };
}
ComSmartPtr<ID2D1DeviceContext1> getDeviceContext() const override
{
return context;
}
ComSmartPtr<ID2D1Image> getDeviceContextTarget() const override
{
return bitmap;
}
RectangleList<int> getPaintAreas() const override
{
return paintAreas;
}
private:
ComSmartPtr<ID2D1DeviceContext1> context;
ComSmartPtr<ID2D1Bitmap1> bitmap;
RectangleList<int> paintAreas;
JUCE_DECLARE_WEAK_REFERENCEABLE (ImagePimpl)
};
//==============================================================================
Direct2DImageContext::Direct2DImageContext (ComSmartPtr<ID2D1DeviceContext1> context,
ComSmartPtr<ID2D1Bitmap1> bitmap,
const RectangleList<int>& paintAreas)
: pimpl (new ImagePimpl { *this, context, bitmap, paintAreas })
{
#if JUCE_DIRECT2D_METRICS
metrics = Direct2DMetricsHub::getInstance()->imageContextMetrics;
#endif
}
Direct2DImageContext::~Direct2DImageContext() = default;
ComSmartPtr<ID2D1DeviceContext1> Direct2DImageContext::getDeviceContext() const
{
return getPimpl()->getDeviceContext();
}
Direct2DGraphicsContext::Pimpl* Direct2DImageContext::getPimpl() const noexcept
{
return pimpl.get();
}
void Direct2DImageContext::clearTargetBuffer()
{
// The bitmap was already cleared when it was created; do nothing here
}
} // namespace juce
@@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
class Direct2DImageContext : public Direct2DGraphicsContext
{
public:
Direct2DImageContext (ComSmartPtr<ID2D1DeviceContext1>,
ComSmartPtr<ID2D1Bitmap1>,
const RectangleList<int>&);
~Direct2DImageContext() override;
ComSmartPtr<ID2D1DeviceContext1> getDeviceContext() const;
private:
struct ImagePimpl;
std::unique_ptr<ImagePimpl> pimpl;
Pimpl* getPimpl() const noexcept override;
void clearTargetBuffer() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DImageContext)
};
} // namespace juce
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,259 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
/* A set of pages that together represent a full virtual bitmap.
All pages in the set always share the same resource context.
Additionally, stores a reference to a software-backed bitmap, the content of which will
be copied to the pages when necessary in order to ensure that the software- and hardware-backed
bitmaps match.
*/
class Direct2DPixelDataPages
{
public:
using Page = Direct2DPixelDataPage;
enum class State
{
unsuitableToRead, // Image data is outdated
suitableToRead, // Image data is up-to-date with the backing data
cleared, // Implies suitableToRead
};
/* Creates a single page containing the provided bitmap and main-memory storage, marking the
hardware data as up-to-date.
*/
Direct2DPixelDataPages (ImagePixelDataBackupExtensions*, ComSmartPtr<ID2D1Bitmap1>, ImagePixelData::Ptr);
/* Allocates hardware storage for the provided software bitmap.
Depending on the initial state, will:
- mark the GPU images as needing to be copied from main memory before they are next accessed, or
- mark the GPU images as up-to-date, or
- clear the GPU images, then mark them as up-to-date
*/
Direct2DPixelDataPages (ImagePixelDataBackupExtensions*, ComSmartPtr<ID2D1Device1>, ImagePixelData::Ptr, State);
/* Returns all pages included in this set.
This will be called before reading from the pages (e.g. when drawing them).
Therefore, this function will check whether the hardware data is out-of-date and
copy from the software image if necessary before returning.
*/
Span<const Page> getPages();
/** Returns all pages without first syncing from main memory. */
Span<const Page> getPagesWithoutSync() const;
/* Marks this set as needing to be updated from the software image.
We don't actually do the copy until the next time that we need to read the hardware pages.
This is to avoid redundant copies in the common case that pages are only drawn on a single
device at a time.
*/
void markOutdated()
{
upToDate = false;
}
bool isUpToDate() const
{
return upToDate;
}
private:
ImagePixelDataBackupExtensions* parentBackupExtensions = nullptr;
ImagePixelData::Ptr backingData;
std::vector<Direct2DPixelDataPage> pages;
bool upToDate = false;
};
/* Pixel data type providing accelerated access to cached Direct2D textures.
Direct2D bitmaps are device-dependent resources, but frequently a computer will
have multiple devices, e.g. if there are several GPUs available which is common for laptops.
In order to support a fast image type that can be drawn by any one of the available devices,
we store a software bitmap which acts as the source-of-truth, and cache per-device hardware
bitmaps alongside it. The caching mechanism tries to minimise the amount of redundant work.
When attempting to access hardware bitmaps, we first check the cache to see whether we've
previously allocated bitmaps for the requested device, and only create bitmaps if none already
exist.
We only copy from the software backup to hardware memory immediately before accessing the
bitmaps for a particular device, and then only if that hardware bitmap is outdated. All
hardware bitmaps are marked as outdated when a writeable BitmapData is created for the current
PixelData. When creating a low-level graphics context, all hardware bitmaps other than the
render target are marked as outdated.
*/
class Direct2DPixelData : public ImagePixelData,
private DxgiAdapterListener,
private ImagePixelDataBackupExtensions
{
public:
using Ptr = ReferenceCountedObjectPtr<Direct2DPixelData>;
using Page = Direct2DPixelDataPage;
using Pages = Direct2DPixelDataPages;
/* Creates image storage, taking ownership of the provided bitmap.
This will immediately copy the content of the image to the software backup, so that the
image can still be drawn if original device goes away.
*/
Direct2DPixelData (ComSmartPtr<ID2D1Device1>, ComSmartPtr<ID2D1Bitmap1>);
/* Creates software image storage of the requested size. */
Direct2DPixelData (Image::PixelFormat, int, int, bool);
~Direct2DPixelData() override;
/* Creates new software image storage with content matching the content of this image.
Does not copy any hardware resources.
*/
ImagePixelData::Ptr clone() override
{
return new Direct2DPixelData (backingData->clone(), State::drawn);
}
std::unique_ptr<ImageType> createType() const override
{
return std::make_unique<NativeImageType>();
}
/* Creates a graphics context that will use the default device to draw into hardware bitmaps
for that device. When the context is destroyed, the rendered hardware bitmap will be copied
back to software storage.
This PixelData may hold device resources for devices other than the default device. In that
case, the other device resources will be marked as outdated, to ensure that they are updated
from the software backup before they are next accessed.
*/
std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override;
/* Provides access to the software image storage.
If the bitmap data provides write access, then all device resources will be marked as
outdated, to ensure that they are updated from the software backup before they are next
accessed.
*/
void initialiseBitmapData (Image::BitmapData&, int, int, Image::BitmapData::ReadWriteMode) override;
void applyGaussianBlurEffectInArea (Rectangle<int>, float) override;
void applySingleChannelBoxBlurEffectInArea (Rectangle<int>, int) override;
void multiplyAllAlphasInArea (Rectangle<int>, float) override;
void desaturateInArea (Rectangle<int>) override;
/* This returns image data that is suitable for use when drawing with the provided context.
This image data should be treated as a read-only view - making modifications directly
through the Direct2D API will have unpredictable results.
If you want to render into this image using D2D, call createLowLevelContext.
*/
Span<const Page> getPagesForDevice (ComSmartPtr<ID2D1Device1>);
/* Utility function that just returns a pointer to the bitmap for the first page returned from
getPagesForContext.
*/
ComSmartPtr<ID2D1Bitmap1> getFirstPageForDevice (ComSmartPtr<ID2D1Device1> device)
{
const auto pages = getPagesForDevice (device);
return ! pages.empty() ? pages.front().bitmap : nullptr;
}
BackupExtensions* getBackupExtensions() override { return this; }
const BackupExtensions* getBackupExtensions() const override { return this; }
ImagePixelDataNativeExtensions getNativeExtensions() override;
private:
enum class State
{
initiallyUndefined,
initiallyCleared,
drawing,
drawn,
outdated,
};
Direct2DPixelData (ImagePixelData::Ptr, State);
auto getIteratorForDevice (ComSmartPtr<ID2D1Device1>);
/* Attempts to copy the content of the corresponding texture in graphics storage into
persistent software storage.
The argument specifies the device holding the texture that should be backed up.
Passing null will instead search through all devices to find which device has the most
recent copy of the image data.
In most cases it is unnecessary to call this function directly.
Returns true on success, i.e. the backup is already up-to-date or the backup was updated
successfully.
Returns false on failure. The backup process may fail if the graphics storage became
unavailable for some reason, such as an external GPU being disconnected, or a remote desktop
session ending. If this happens, the image content is *irrevocably lost* and will need to
be recreated.
*/
bool createPersistentBackup (ComSmartPtr<ID2D1Device1> deviceHint);
struct Context;
std::unique_ptr<Context> createNativeContext();
template <typename Fn>
bool applyEffectInArea (Rectangle<int>, Fn&&);
void setBackupEnabled (bool) override;
bool isBackupEnabled() const override;
bool backupNow() override;
bool needsBackup() const override;
bool canBackup() const override;
void adapterCreated (DxgiAdapter::Ptr) override {}
void adapterRemoved (DxgiAdapter::Ptr adapter) override
{
if (adapter != nullptr)
pagesForDevice.erase (adapter->direct2DDevice);
if (mostRecentDevice == adapter->direct2DDevice)
mostRecentDevice = nullptr;
}
SharedResourcePointer<DirectX> directX;
ImagePixelData::Ptr backingData;
ComSmartPtr<ID2D1Device1> mostRecentDevice;
std::map<ComSmartPtr<ID2D1Device1>, Pages> pagesForDevice;
State state;
bool sync = true;
JUCE_LEAK_DETECTOR (Direct2DPixelData)
};
} // namespace juce
@@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#if JUCE_DIRECT2D_METRICS
namespace juce
{
String Direct2DMetricsHub::getProcessString() noexcept
{
auto processID = GetCurrentProcessId();
return String::toHexString ((pointer_sized_int) processID);
}
void Direct2DMetricsHub::HubPipeServer::messageReceived (const MemoryBlock& message)
{
int requestType = *(int*) message.getData();
switch (requestType)
{
case getValuesRequest:
{
ScopedLock locker { owner.lock };
auto foregroundWindow = GetForegroundWindow();
Direct2DMetrics::Ptr metrics = nullptr;
for (int i = 0; i < owner.metricsArray.size(); ++i)
{
auto arrayEntry = owner.metricsArray[i];
if (arrayEntry->windowHandle && arrayEntry->windowHandle == foregroundWindow)
{
metrics = arrayEntry;
break;
}
}
if (! metrics)
{
if (owner.lastMetrics && owner.metricsArray.contains (owner.lastMetrics))
metrics = owner.lastMetrics;
}
if (metrics)
{
MemoryBlock block { sizeof (GetValuesResponse), true };
auto* response = (GetValuesResponse*) block.getData();
response->responseType = getValuesRequest;
response->windowHandle = metrics->windowHandle;
for (size_t i = 0; i <= Direct2DMetrics::drawGlyphRunTime; ++i)
{
auto& accumulator = metrics->getAccumulator (i);
response->values[i].count = accumulator.getCount();
response->values[i].total = metrics->getSum (i);
response->values[i].average = accumulator.getAverage();
response->values[i].minimum = accumulator.getMinValue();
response->values[i].maximum = accumulator.getMaxValue();
response->values[i].stdDev = accumulator.getStandardDeviation();
}
// Track bitmap operations common to all device contexts
for (size_t i = Direct2DMetrics::createBitmapTime; i <= Direct2DMetrics::unmapBitmapTime; ++i)
{
auto& accumulator = owner.imageContextMetrics->getAccumulator (i);
response->values[i].count = accumulator.getCount();
response->values[i].total = metrics->getSum (i);
response->values[i].average = accumulator.getAverage();
response->values[i].minimum = accumulator.getMinValue();
response->values[i].maximum = accumulator.getMaxValue();
response->values[i].stdDev = accumulator.getStandardDeviation();
}
sendMessage (block);
owner.lastMetrics = metrics.get();
}
break;
}
case resetValuesRequest:
{
owner.resetAll();
break;
}
}
}
void Direct2DMetricsHub::resetAll()
{
ScopedLock locker { lock };
imageContextMetrics->reset();
for (auto metrics : metricsArray)
{
metrics->reset();
}
}
} // namespace juce
#endif
@@ -0,0 +1,320 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
#if JUCE_DIRECT2D_METRICS
namespace juce
{
struct Direct2DMetrics : public ReferenceCountedObject
{
using Ptr = ReferenceCountedObjectPtr<Direct2DMetrics>;
#define DIRECT2D_PAINT_STAT_LIST \
DIRECT2D_PAINT_STAT (messageThreadPaintDuration) \
DIRECT2D_PAINT_STAT (swapChainThreadTime) \
DIRECT2D_PAINT_STAT (frameInterval) \
DIRECT2D_PAINT_STAT (endDrawDuration) \
DIRECT2D_PAINT_STAT (present1Duration) \
DIRECT2D_PAINT_STAT (createGeometryTime) \
DIRECT2D_PAINT_STAT (drawGeometryTime) \
DIRECT2D_PAINT_STAT (fillGeometryTime) \
DIRECT2D_PAINT_STAT (createFilledGRTime) \
DIRECT2D_PAINT_STAT (createStrokedGRTime) \
DIRECT2D_PAINT_STAT (drawGRTime) \
DIRECT2D_PAINT_STAT (createGradientTime) \
DIRECT2D_PAINT_STAT (pushAliasedAxisAlignedLayerTime) \
DIRECT2D_PAINT_STAT (pushGeometryLayerTime) \
DIRECT2D_PAINT_STAT (fillTranslatedRectTime) \
DIRECT2D_PAINT_STAT (fillAxisAlignedRectTime) \
DIRECT2D_PAINT_STAT (fillTransformedRectTime) \
DIRECT2D_PAINT_STAT (fillRectListTime) \
DIRECT2D_PAINT_STAT (drawImageTime) \
DIRECT2D_PAINT_STAT (spriteBatchTime) \
DIRECT2D_PAINT_STAT (spriteBatchSetupTime) \
DIRECT2D_PAINT_STAT (createSpriteSourceTime) \
DIRECT2D_PAINT_STAT (setSpritesTime) \
DIRECT2D_PAINT_STAT (addSpritesTime) \
DIRECT2D_PAINT_STAT (clearSpritesTime) \
DIRECT2D_PAINT_STAT (drawSpritesTime) \
DIRECT2D_PAINT_STAT (drawGlyphRunTime) \
DIRECT2D_PAINT_STAT (createBitmapTime) \
DIRECT2D_PAINT_STAT (mapBitmapTime) \
DIRECT2D_LAST_PAINT_STAT (unmapBitmapTime)
#define DIRECT2D_PAINT_STAT(name) name,
#define DIRECT2D_LAST_PAINT_STAT(name) name
enum
{
DIRECT2D_PAINT_STAT_LIST,
numStats
};
#undef DIRECT2D_PAINT_STAT
#undef DIRECT2D_LAST_PAINT_STAT
#define DIRECT2D_PAINT_STAT(name) #name,
#define DIRECT2D_LAST_PAINT_STAT(name) #name
StringArray const accumulatorNames { DIRECT2D_PAINT_STAT_LIST };
#undef DIRECT2D_PAINT_STAT
#undef DIRECT2D_LAST_PAINT_STAT
CriticalSection& lock;
String const name;
void* const windowHandle;
int64 const creationTime = Time::getMillisecondCounter();
double const millisecondsPerTick = 1000.0 / (double) Time::getHighResolutionTicksPerSecond();
int paintCount = 0;
int presentCount = 0;
int present1Count = 0;
int64 lastPaintStartTicks = 0;
uint64 lockAcquireMaxTicks = 0;
Direct2DMetrics (CriticalSection& lockIn, String nameIn, void* windowHandleIn)
: lock (lockIn),
name (nameIn),
windowHandle (windowHandleIn)
{
}
~Direct2DMetrics() = default;
void startFrame()
{
ScopedLock locker { lock };
zerostruct (sums);
}
void finishFrame()
{
}
void reset()
{
ScopedLock locker { lock };
for (auto& accumulator : runningAccumulators)
accumulator.reset();
lastPaintStartTicks = 0;
paintCount = 0;
present1Count = 0;
lockAcquireMaxTicks = 0;
}
auto& getAccumulator (size_t index) noexcept
{
return runningAccumulators[index];
}
auto getSum (size_t index) const noexcept
{
return sums[index];
}
void addValueTicks (size_t index, int64 ticks)
{
addValueMsec (index, Time::highResolutionTicksToSeconds (ticks) * 1000.0);
}
void addValueMsec (size_t index, double value)
{
ScopedLock locker { lock };
auto& accumulator = runningAccumulators[index];
switch (index)
{
case frameInterval:
if (accumulator.getCount() > 100)
{
accumulator.reset();
}
break;
}
accumulator.addValue (value);
sums[index] += value;
}
private:
std::array<StatisticsAccumulator<double>, numStats> runningAccumulators;
std::array<double, numStats> sums;
};
struct Direct2DScopedElapsedTime
{
Direct2DScopedElapsedTime (Direct2DMetrics::Ptr& metricsIn, size_t accumulatorIndexIn)
: metrics (metricsIn.get()),
accumulatorIndex (accumulatorIndexIn)
{
}
Direct2DScopedElapsedTime (Direct2DMetrics* metricsIn, size_t accumulatorIndexIn)
: metrics (metricsIn),
accumulatorIndex (accumulatorIndexIn)
{
}
~Direct2DScopedElapsedTime()
{
auto finishTicks = Time::getHighResolutionTicks();
metrics->addValueTicks (accumulatorIndex, finishTicks - startTicks);
}
int64 startTicks = Time::getHighResolutionTicks();
Direct2DMetrics* metrics;
size_t accumulatorIndex;
};
class Direct2DMetricsHub : public DeletedAtShutdown
{
public:
Direct2DMetricsHub()
{
imageContextMetrics = new Direct2DMetrics { lock, "Image " + getProcessString(), nullptr };
add (imageContextMetrics);
}
~Direct2DMetricsHub() override
{
clearSingletonInstance();
}
void add (Direct2DMetrics::Ptr metrics)
{
metricsArray.insert (0, metrics);
}
void remove (Direct2DMetrics::Ptr metrics)
{
metricsArray.removeObject (metrics);
}
Direct2DMetrics::Ptr getMetricsForWindowHandle (void* windowHandle) noexcept
{
for (auto& metrics : metricsArray)
if (metrics->windowHandle == windowHandle)
return metrics;
return nullptr;
}
enum
{
getValuesRequest,
resetValuesRequest
};
struct MetricValues
{
size_t count;
double total;
double average;
double minimum;
double maximum;
double stdDev;
};
struct GetValuesResponse
{
int responseType;
void* windowHandle;
MetricValues values[Direct2DMetrics::numStats];
};
CriticalSection lock;
Direct2DMetrics::Ptr imageContextMetrics;
static constexpr int magicNumber = 0xd2d1;
JUCE_DECLARE_SINGLETON_INLINE (Direct2DMetricsHub, false)
private:
static String getProcessString() noexcept;
void resetAll();
struct HubPipeServer : public InterprocessConnection
{
explicit HubPipeServer (Direct2DMetricsHub& ownerIn)
: InterprocessConnection (false, magicNumber),
owner (ownerIn)
{
createPipe ("JUCEDirect2DMetricsHub:" + owner.getProcessString(), -1, true);
}
~HubPipeServer() override
{
disconnect();
}
void connectionMade() override
{
}
void connectionLost() override
{
}
void messageReceived (const MemoryBlock& message) override;
Direct2DMetricsHub& owner;
};
HubPipeServer hubPipeServer { *this };
ReferenceCountedArray<Direct2DMetrics> metricsArray;
Direct2DMetrics* lastMetrics = nullptr;
};
} // namespace juce
#define JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME(metrics, name) juce::Direct2DScopedElapsedTime scopedElapsedTime_##name { metrics, juce::Direct2DMetrics::name };
#else
namespace juce
{
struct Direct2DMetrics : public ReferenceCountedObject
{
using Ptr = ReferenceCountedObjectPtr<Direct2DMetrics>;
};
} // namespace juce
#define JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME(metrics, name)
#endif
@@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
/* A single bitmap that represents a subsection of a virtual bitmap. */
struct Direct2DPixelDataPage
{
/* The bounds of the stored bitmap inside the virtual bitmap. */
Rectangle<int> getBounds() const
{
if (bitmap == nullptr)
return {};
const auto size = bitmap->GetPixelSize();
return Rectangle { (int) size.width, (int) size.height }.withPosition (topLeft);
}
/* The stored subsection bitmap. */
ComSmartPtr<ID2D1Bitmap1> bitmap;
/* The top-left position of this virtual bitmap inside the virtual bitmap. */
Point<int> topLeft;
};
} // namespace juce
@@ -0,0 +1,585 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
static ComSmartPtr<ID2D1GradientStopCollection> makeGradientStopCollection (const ColourGradient& gradient,
ComSmartPtr<ID2D1DeviceContext1> deviceContext,
[[maybe_unused]] Direct2DMetrics* metrics) noexcept
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createGradientTime);
const int numColors = gradient.getNumColours();
std::vector<D2D1_GRADIENT_STOP> stops ((size_t) numColors);
for (auto [index, stop] : enumerate (stops, int{}))
{
stop.color = D2DUtilities::toCOLOR_F (gradient.getColour (index));
stop.position = (FLOAT) gradient.getColourPosition (index);
}
ComSmartPtr<ID2D1GradientStopCollection> result;
deviceContext->CreateGradientStopCollection (stops.data(), (UINT32) stops.size(), result.resetAndGetPointerAddress());
return result;
}
class LinearGradientCache
{
public:
ComSmartPtr<ID2D1LinearGradientBrush> get (const ColourGradient& gradient,
ComSmartPtr<ID2D1DeviceContext1> deviceContext,
Direct2DMetrics* metrics)
{
jassert (! gradient.isRadial);
return cache.get (gradient, [&deviceContext, &metrics] (const auto& key)
{
const auto gradientStops = makeGradientStopCollection (key, deviceContext, metrics);
const auto p1 = key.point1;
const auto p2 = key.point2;
const auto linearGradientBrushProperties = D2D1::LinearGradientBrushProperties ({ p1.x, p1.y }, { p2.x, p2.y });
const D2D1_BRUSH_PROPERTIES brushProps { 1.0f, D2D1::IdentityMatrix() };
ComSmartPtr<ID2D1LinearGradientBrush> result;
deviceContext->CreateLinearGradientBrush (linearGradientBrushProperties,
brushProps,
gradientStops,
result.resetAndGetPointerAddress());
return result;
});
}
private:
LruCache<ColourGradient, ComSmartPtr<ID2D1LinearGradientBrush>> cache;
};
class RadialGradientCache
{
public:
ComSmartPtr<ID2D1RadialGradientBrush> get (const ColourGradient& gradient,
ComSmartPtr<ID2D1DeviceContext1> deviceContext,
Direct2DMetrics* metrics)
{
jassert (gradient.isRadial);
return cache.get (gradient, [&deviceContext, &metrics] (const auto& key)
{
const auto gradientStops = makeGradientStopCollection (key, deviceContext, metrics);
const auto p1 = key.point1;
const auto p2 = key.point2;
const auto r = p1.getDistanceFrom (p2);
const auto radialGradientBrushProperties = D2D1::RadialGradientBrushProperties ({ p1.x, p1.y }, {}, r, r);
const D2D1_BRUSH_PROPERTIES brushProps { 1.0F, D2D1::IdentityMatrix() };
ComSmartPtr<ID2D1RadialGradientBrush> result;
deviceContext->CreateRadialGradientBrush (radialGradientBrushProperties,
brushProps,
gradientStops,
result.resetAndGetPointerAddress());
return result;
});
}
private:
LruCache<ColourGradient, ComSmartPtr<ID2D1RadialGradientBrush>> cache;
};
class RectangleListSpriteBatch
{
public:
RectangleListSpriteBatch() = default;
~RectangleListSpriteBatch()
{
release();
}
void release()
{
whiteRectangle = nullptr;
spriteBatches = {};
destinations.free();
destinationsCapacity = 0;
}
template <typename TransformRectangle>
void fillRectangles (ComSmartPtr<ID2D1DeviceContext1> deviceContext,
const RectangleList<float>& rectangles,
const Colour colour,
TransformRectangle&& transformRectangle,
[[maybe_unused]] Direct2DMetrics* metrics)
{
if (rectangles.isEmpty())
return;
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, spriteBatchTime)
auto numRectanglesPainted = 0;
while (numRectanglesPainted < rectangles.getNumRectangles())
{
auto numRectanglesRemaining = rectangles.getNumRectangles() - numRectanglesPainted;
auto spriteBatchSize = isPowerOfTwo (numRectanglesRemaining) ? numRectanglesRemaining : (nextPowerOfTwo (numRectanglesRemaining) >> 1);
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, spriteBatchSetupTime);
if (destinationsCapacity < (size_t) spriteBatchSize)
{
destinations.calloc (spriteBatchSize);
destinationsCapacity = (size_t) spriteBatchSize;
}
auto destination = destinations.getData();
for (int i = numRectanglesPainted; i < numRectanglesPainted + spriteBatchSize; ++i)
{
auto r = rectangles.getRectangle (i);
r = transformRectangle (r);
*destination = D2DUtilities::toRECT_F (r);
++destination;
}
}
if (! whiteRectangle)
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createSpriteSourceTime);
auto hr = deviceContext->CreateCompatibleRenderTarget (D2D1_SIZE_F { (float) rectangleSize, (float) rectangleSize },
D2D1_SIZE_U { rectangleSize, rectangleSize },
D2D1_PIXEL_FORMAT { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED },
whiteRectangle.resetAndGetPointerAddress());
if (FAILED (hr))
return;
whiteRectangle->BeginDraw();
whiteRectangle->Clear (D2D1_COLOR_F { 1.0f, 1.0f, 1.0f, 1.0f });
whiteRectangle->EndDraw();
}
ComSmartPtr<ID2D1Bitmap> bitmap;
if (auto hr = whiteRectangle->GetBitmap (bitmap.resetAndGetPointerAddress()); SUCCEEDED (hr))
{
ComSmartPtr<ID2D1DeviceContext3> deviceContext3;
if (hr = deviceContext->QueryInterface<ID2D1DeviceContext3> (deviceContext3.resetAndGetPointerAddress()); SUCCEEDED (hr))
{
auto d2dColour = D2DUtilities::toCOLOR_F (colour);
auto spriteBatch = getSpriteBatch (*deviceContext3, (uint32) spriteBatchSize);
if (spriteBatch == nullptr)
return;
auto setCount = jmin ((uint32) spriteBatchSize, spriteBatch->GetSpriteCount());
auto addCount = (uint32) spriteBatchSize > setCount ? (uint32) spriteBatchSize - setCount : 0;
if (setCount != 0)
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, setSpritesTime);
spriteBatch->SetSprites (0, setCount, destinations.getData(), nullptr, &d2dColour, nullptr, sizeof (D2D1_RECT_F), 0, 0, 0);
}
if (addCount != 0)
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, addSpritesTime);
spriteBatch->AddSprites (addCount, destinations.getData() + setCount, nullptr, &d2dColour, nullptr, sizeof (D2D1_RECT_F), 0, 0, 0);
}
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, drawSpritesTime);
deviceContext3->SetAntialiasMode (D2D1_ANTIALIAS_MODE_ALIASED);
deviceContext3->DrawSpriteBatch (spriteBatch, bitmap);
deviceContext3->SetAntialiasMode (D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
}
}
numRectanglesPainted += spriteBatchSize;
}
}
private:
ComSmartPtr<ID2D1SpriteBatch> getSpriteBatch (ID2D1DeviceContext3& dc, uint32 key)
{
return spriteBatches.get ((uint32) key, [&dc] (auto) -> ComSmartPtr<ID2D1SpriteBatch>
{
ComSmartPtr<ID2D1SpriteBatch> result;
if (const auto hr = dc.CreateSpriteBatch (result.resetAndGetPointerAddress()); SUCCEEDED (hr))
return result;
return nullptr;
});
}
static constexpr uint32 rectangleSize = 32;
ComSmartPtr<ID2D1BitmapRenderTarget> whiteRectangle;
HeapBlock<D2D1_RECT_F> destinations;
size_t destinationsCapacity = 0;
LruCache<uint32, ComSmartPtr<ID2D1SpriteBatch>, 8> spriteBatches;
};
class Direct2DDeviceResources
{
public:
static DxgiAdapter::Ptr findAdapter (const DxgiAdapters& adapters, ID2D1Bitmap1* bitmap)
{
if (bitmap == nullptr)
return {};
ComSmartPtr<IDXGISurface> surface;
bitmap->GetSurface (surface.resetAndGetPointerAddress());
if (surface == nullptr)
return {};
ComSmartPtr<IDXGIDevice> device;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
surface->GetDevice (__uuidof (device), (void**) device.resetAndGetPointerAddress());
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return findAdapter (adapters, device);
}
static DxgiAdapter::Ptr findAdapter (const DxgiAdapters& dxgiAdapters, IDXGIDevice* dxgiDevice)
{
if (dxgiDevice == nullptr)
return {};
ComSmartPtr<IDXGIAdapter> adapter;
dxgiDevice->GetAdapter (adapter.resetAndGetPointerAddress());
if (adapter == nullptr)
return {};
ComSmartPtr<IDXGIAdapter1> adapter1;
adapter.QueryInterface (adapter1);
if (adapter1 == nullptr)
return {};
const auto adapterLuid = getLUID (adapter1);
const auto& adapters = dxgiAdapters.getAdapterArray();
const auto it = std::find_if (adapters.begin(), adapters.end(), [&] (DxgiAdapter::Ptr ptr)
{
const auto tie = [] (const LUID& x) { return std::tie (x.LowPart, x.HighPart); };
const auto thisLuid = getLUID (ptr->dxgiAdapter);
return tie (thisLuid) == tie (adapterLuid);
});
if (it == adapters.end())
return {};
return *it;
}
static DxgiAdapter::Ptr findAdapter (const DxgiAdapters& dxgiAdapters, ID2D1DeviceContext1* context)
{
if (context == nullptr)
return {};
ComSmartPtr<ID2D1Device> device;
context->GetDevice (device.resetAndGetPointerAddress());
if (device == nullptr)
return {};
ComSmartPtr<IDXGIDevice> dxgiDevice;
device.QueryInterface (dxgiDevice);
return findAdapter (dxgiAdapters, dxgiDevice);
}
static LUID getLUID (ComSmartPtr<IDXGIAdapter1> adapter)
{
DXGI_ADAPTER_DESC1 desc{};
adapter->GetDesc1 (&desc);
return desc.AdapterLuid;
}
static std::optional<Direct2DDeviceResources> create (ComSmartPtr<ID2D1DeviceContext1> context)
{
if (context == nullptr)
return {};
Direct2DDeviceResources result;
if (const auto hr = context->CreateSolidColorBrush (D2D1::ColorF (0.0f, 0.0f, 0.0f, 1.0f),
result.colourBrush.resetAndGetPointerAddress());
FAILED (hr))
{
jassertfalse;
return {};
}
result.rectangleListSpriteBatch = std::make_unique<RectangleListSpriteBatch>();
return result;
}
ComSmartPtr<ID2D1SolidColorBrush> colourBrush;
LinearGradientCache linearGradientCache;
RadialGradientCache radialGradientCache;
std::unique_ptr<RectangleListSpriteBatch> rectangleListSpriteBatch;
private:
Direct2DDeviceResources() = default;
};
class SwapChain
{
public:
SwapChain() = default;
HRESULT create (HWND hwnd, Rectangle<int> size, DxgiAdapter::Ptr adapter)
{
if (chain != nullptr || hwnd == nullptr)
return S_OK;
SharedResourcePointer<DirectX> directX;
auto dxgiFactory = directX->adapters.getFactory();
if (dxgiFactory == nullptr || adapter->direct3DDevice == nullptr)
return E_FAIL;
buffer = nullptr;
chain = nullptr;
// Make the waitable swap chain
// Create the swap chain with premultiplied alpha support for transparent windows
DXGI_SWAP_CHAIN_DESC1 swapChainDescription = {};
swapChainDescription.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDescription.Width = (UINT) size.getWidth();
swapChainDescription.Height = (UINT) size.getHeight();
swapChainDescription.SampleDesc.Count = 1;
swapChainDescription.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDescription.BufferCount = 2;
swapChainDescription.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDescription.Flags = swapChainFlags;
swapChainDescription.Scaling = DXGI_SCALING_STRETCH;
swapChainDescription.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
if (const auto hr = dxgiFactory->CreateSwapChainForComposition (adapter->direct3DDevice,
&swapChainDescription,
nullptr,
chain.resetAndGetPointerAddress());
FAILED (hr))
{
return hr;
}
// Get the waitable swap chain presentation event and set the maximum frame latency
ComSmartPtr<IDXGISwapChain2> chain2;
if (const auto hr = chain.QueryInterface (chain2); FAILED (hr))
return hr;
if (chain2 == nullptr)
return E_FAIL;
swapChainEvent.emplace (chain2->GetFrameLatencyWaitableObject());
if (swapChainEvent->getHandle() == INVALID_HANDLE_VALUE)
return E_NOINTERFACE;
chain2->SetMaximumFrameLatency (1);
createBuffer (adapter);
return buffer != nullptr ? S_OK : E_FAIL;
}
bool canPaint() const
{
return chain != nullptr && buffer != nullptr;
}
HRESULT resize (Rectangle<int> newSize)
{
if (chain == nullptr)
return E_FAIL;
constexpr auto minFrameSize = 1;
constexpr auto maxFrameSize = 16384;
auto scaledSize = newSize.getUnion ({ minFrameSize, minFrameSize })
.getIntersection ({ maxFrameSize, maxFrameSize });
buffer = nullptr;
if (const auto hr = chain->ResizeBuffers (0, (UINT) scaledSize.getWidth(), (UINT) scaledSize.getHeight(), DXGI_FORMAT_B8G8R8A8_UNORM, swapChainFlags); FAILED (hr))
return hr;
ComSmartPtr<IDXGIDevice> device;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
chain->GetDevice (__uuidof (device), (void**) device.resetAndGetPointerAddress());
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
SharedResourcePointer<DirectX> directX;
createBuffer (Direct2DDeviceResources::findAdapter (directX->adapters, device));
return buffer != nullptr ? S_OK : E_FAIL;
}
Rectangle<int> getSize() const
{
const auto surface = getSurface();
if (surface == nullptr)
return {};
DXGI_SURFACE_DESC desc{};
if (FAILED (surface->GetDesc (&desc)))
return {};
return { (int) desc.Width, (int) desc.Height };
}
WindowsScopedEvent* getEvent()
{
if (swapChainEvent.has_value())
return &*swapChainEvent;
return nullptr;
}
auto getChain() const
{
return chain;
}
ComSmartPtr<ID2D1Bitmap1> getBuffer() const
{
return buffer;
}
static constexpr uint32 swapChainFlags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
static constexpr uint32 presentSyncInterval = 1;
static constexpr uint32 presentFlags = 0;
private:
ComSmartPtr<IDXGISurface> getSurface() const
{
if (chain == nullptr)
return nullptr;
ComSmartPtr<IDXGISurface> surface;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (const auto hr = chain->GetBuffer (0, __uuidof (surface), reinterpret_cast<void**> (surface.resetAndGetPointerAddress())); FAILED (hr))
return nullptr;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return surface;
}
void createBuffer (DxgiAdapter::Ptr adapter)
{
buffer = nullptr;
const auto deviceContext = Direct2DDeviceContext::create (adapter);
if (deviceContext == nullptr)
return;
const auto surface = getSurface();
if (surface == nullptr)
return;
D2D1_BITMAP_PROPERTIES1 bitmapProperties{};
bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
bitmapProperties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
deviceContext->CreateBitmapFromDxgiSurface (surface, bitmapProperties, buffer.resetAndGetPointerAddress());
}
ComSmartPtr<IDXGISwapChain1> chain;
ComSmartPtr<ID2D1Bitmap1> buffer;
std::optional<WindowsScopedEvent> swapChainEvent;
};
//==============================================================================
/* DirectComposition
Using DirectComposition enables transparent windows and smoother window
resizing
This class builds a simple DirectComposition tree that ultimately contains
the swap chain
*/
class CompositionTree
{
public:
static std::optional<CompositionTree> create (IDXGIDevice* dxgiDevice, HWND hwnd, IDXGISwapChain1* swapChain)
{
if (dxgiDevice == nullptr)
return {};
CompositionTree result;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (const auto hr = DCompositionCreateDevice (dxgiDevice,
__uuidof (IDCompositionDevice),
reinterpret_cast<void**> (result.compositionDevice.resetAndGetPointerAddress()));
FAILED (hr))
{
return {};
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
if (const auto hr = result.compositionDevice->CreateTargetForHwnd (hwnd, FALSE, result.compositionTarget.resetAndGetPointerAddress()); FAILED (hr))
return {};
if (const auto hr = result.compositionDevice->CreateVisual (result.compositionVisual.resetAndGetPointerAddress()); FAILED (hr))
return {};
if (const auto hr = result.compositionTarget->SetRoot (result.compositionVisual); FAILED (hr))
return {};
if (const auto hr = result.compositionVisual->SetContent (swapChain); FAILED (hr))
return {};
if (const auto hr = result.compositionDevice->Commit(); FAILED (hr))
return {};
return result;
}
private:
CompositionTree() = default;
ComSmartPtr<IDCompositionDevice> compositionDevice;
ComSmartPtr<IDCompositionTarget> compositionTarget;
ComSmartPtr<IDCompositionVisual> compositionVisual;
};
} // namespace juce
@@ -0,0 +1,991 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
static String getLocalisedName (IDWriteLocalizedStrings* names)
{
jassert (names != nullptr);
uint32 index = 0;
BOOL exists = false;
[[maybe_unused]] auto hr = names->FindLocaleName (L"en-us", &index, &exists);
if (! exists)
index = 0;
uint32 length = 0;
hr = names->GetStringLength (index, &length);
HeapBlock<wchar_t> name (length + 1);
hr = names->GetString (index, name, length + 1);
return static_cast<const wchar_t*> (name);
}
static String getFontFamilyName (IDWriteFontFamily* family)
{
jassert (family != nullptr);
ComSmartPtr<IDWriteLocalizedStrings> familyNames;
auto hr = family->GetFamilyNames (familyNames.resetAndGetPointerAddress());
jassertquiet (SUCCEEDED (hr));
return getLocalisedName (familyNames);
}
static String getFontFaceName (IDWriteFont* font)
{
jassert (font != nullptr);
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
auto hr = font->GetFaceNames (faceNames.resetAndGetPointerAddress());
jassertquiet (SUCCEEDED (hr));
return getLocalisedName (faceNames);
}
template <typename Range>
static StringArray stringArrayFromRange (Range&& range)
{
StringArray result;
for (const auto& item : range)
result.add (item);
return result;
}
class AggregateFontCollection
{
public:
explicit AggregateFontCollection (ComSmartPtr<IDWriteFontCollection> baseCollection)
: collections { std::move (baseCollection) } {}
StringArray findAllTypefaceNames()
{
const std::scoped_lock lock { mutex };
std::set<String> strings;
for (const auto& collection : collections)
{
const auto count = collection->GetFontFamilyCount();
for (auto i = decltype (count){}; i < count; ++i)
{
ComSmartPtr<IDWriteFontFamily> family;
if (FAILED (collection->GetFontFamily (i, family.resetAndGetPointerAddress())) || family == nullptr)
continue;
strings.insert (getFontFamilyName (family));
}
}
return stringArrayFromRange (strings);
}
static std::vector<ComSmartPtr<IDWriteFont>> getAllFontsInFamily (IDWriteFontFamily* fontFamily)
{
const auto fontFacesCount = fontFamily->GetFontCount();
std::vector<ComSmartPtr<IDWriteFont>> result;
result.reserve (fontFacesCount);
for (UINT32 i = 0; i < fontFacesCount; ++i)
{
ComSmartPtr<IDWriteFont> dwFont;
if (FAILED (fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress())))
continue;
if (dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE)
continue;
result.push_back (dwFont);
}
return result;
}
StringArray findAllTypefaceStyles (const String& family)
{
const std::scoped_lock lock { mutex };
for (const auto& collection : collections)
{
BOOL fontFound = false;
uint32 fontIndex = 0;
if (FAILED (collection->FindFamilyName (family.toWideCharPointer(), &fontIndex, &fontFound)) || ! fontFound)
continue;
ComSmartPtr<IDWriteFontFamily> fontFamily;
if (FAILED (collection->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress())) || fontFamily == nullptr)
continue;
std::set<String> uniqueResults;
StringArray orderedResults;
for (const auto& font : getAllFontsInFamily (fontFamily))
{
const auto name = getFontFaceName (font);
if (uniqueResults.insert (name).second)
orderedResults.add (name);
}
return orderedResults;
}
return {};
}
ComSmartPtr<IDWriteFontFamily> getFamilyByName (const wchar_t* name)
{
const std::scoped_lock lock { mutex };
for (const auto& collection : collections)
{
const auto fontIndex = [&]
{
BOOL found = false;
UINT32 index = 0;
return (SUCCEEDED (collection->FindFamilyName (name, &index, &found)) && found)
? index
: (UINT32) -1;
}();
if (fontIndex == (UINT32) -1)
continue;
ComSmartPtr<IDWriteFontFamily> family;
if (FAILED (collection->GetFontFamily (fontIndex, family.resetAndGetPointerAddress())) || family == nullptr)
continue;
return family;
}
return {};
}
ComSmartPtr<IDWriteFont> findFontForFace (IDWriteFontFace* face)
{
for (const auto& collection : collections)
{
ComSmartPtr<IDWriteFont> result;
if (SUCCEEDED (collection->GetFontFromFontFace (face, result.resetAndGetPointerAddress())))
return result;
}
return {};
}
void addCollection (ComSmartPtr<IDWriteFontCollection> collection)
{
const std::scoped_lock lock { mutex };
collections.push_back (std::move (collection));
}
void removeCollection (ComSmartPtr<IDWriteFontCollection> collection)
{
const std::scoped_lock lock { mutex };
const auto iter = std::find (collections.begin(), collections.end(), collection);
if (iter != collections.end())
collections.erase (iter);
}
struct MapResult
{
ComSmartPtr<IDWriteFont> font;
UINT32 length{};
float scale{};
};
/* Tries matching against each collection in turn.
If any collection is able to match the entire string, then uses the appropriate font
from that collection.
Otherwise, returns the font that is able to match the longest sequence of characters,
preferring user-provided fonts.
*/
MapResult mapCharacters (IDWriteFontFallback* fallback,
IDWriteTextAnalysisSource* analysisSource,
UINT32 textPosition,
UINT32 textLength,
wchar_t const* baseFamilyName,
DWRITE_FONT_WEIGHT baseWeight,
DWRITE_FONT_STYLE baseStyle,
DWRITE_FONT_STRETCH baseStretch) noexcept
{
const std::scoped_lock lock { mutex };
// For reasons I don't understand, the system may pick better substitutions when passing
// nullptr, instead of the system collection, as the "default collection to use".
auto collectionsToCheck = collections;
collectionsToCheck.insert (collectionsToCheck.begin(), nullptr);
MapResult bestMatch;
for (const auto& collection : collectionsToCheck)
{
MapResult result;
const auto status = fallback->MapCharacters (analysisSource,
textPosition,
textLength,
collection,
baseFamilyName,
baseWeight,
baseStyle,
baseStretch,
&result.length,
result.font.resetAndGetPointerAddress(),
&result.scale);
if (FAILED (status) || result.font == nullptr)
continue;
if (result.length == textLength)
return result;
if (result.length >= bestMatch.length)
bestMatch = result;
}
return bestMatch;
}
private:
std::vector<ComSmartPtr<IDWriteFontCollection>> collections;
std::mutex mutex;
};
class MemoryFontFileStream final : public ComBaseClassHelper<IDWriteFontFileStream>
{
public:
explicit MemoryFontFileStream (std::shared_ptr<const MemoryBlock> blockIn)
: block (std::move (blockIn))
{
}
JUCE_COMRESULT GetFileSize (UINT64* fileSize) noexcept override
{
*fileSize = block->getSize();
return S_OK;
}
JUCE_COMRESULT GetLastWriteTime (UINT64* lastWriteTime) noexcept override
{
*lastWriteTime = 0;
return S_OK;
}
JUCE_COMRESULT ReadFileFragment (const void** fragmentStart,
UINT64 fileOffset,
UINT64 fragmentSize,
void** fragmentContext) noexcept override
{
if (fileOffset + fragmentSize > block->getSize())
{
*fragmentStart = nullptr;
*fragmentContext = nullptr;
return E_INVALIDARG;
}
*fragmentStart = addBytesToPointer (block->getData(), fileOffset);
*fragmentContext = this;
return S_OK;
}
void WINAPI ReleaseFileFragment (void*) noexcept override {}
private:
std::shared_ptr<const MemoryBlock> block;
};
class MemoryFontFileLoader final : public ComBaseClassHelper<IDWriteFontFileLoader>
{
public:
explicit MemoryFontFileLoader (MemoryBlock blob)
: block (std::make_shared<const MemoryBlock> (std::move (blob)))
{
}
HRESULT WINAPI CreateStreamFromKey (const void* fontFileReferenceKey,
UINT32 keySize,
IDWriteFontFileStream** fontFileStream) noexcept override
{
if (keySize != Uuid::size())
return E_INVALIDARG;
Uuid requestedKey { static_cast<const uint8*> (fontFileReferenceKey) };
if (requestedKey == uuid)
{
*fontFileStream = new MemoryFontFileStream { block };
return S_OK;
}
return E_INVALIDARG;
}
Uuid getUuid() const { return uuid; }
private:
std::shared_ptr<const MemoryBlock> block;
Uuid uuid;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryFontFileLoader)
};
class FontFileEnumerator final : public ComBaseClassHelper<IDWriteFontFileEnumerator>
{
public:
FontFileEnumerator (IDWriteFactory& factoryIn, ComSmartPtr<MemoryFontFileLoader> loaderIn)
: factory (factoryIn), loader (loaderIn) {}
HRESULT WINAPI GetCurrentFontFile (IDWriteFontFile** fontFile) noexcept override
{
*fontFile = nullptr;
if (! isPositiveAndBelow (rawDataIndex, 1))
return E_FAIL;
const auto uuid = loader->getUuid();
return factory.CreateCustomFontFileReference (uuid.getRawData(),
(UINT32) uuid.size(),
loader,
fontFile);
}
HRESULT WINAPI MoveNext (BOOL* hasCurrentFile) noexcept override
{
++rawDataIndex;
*hasCurrentFile = rawDataIndex < 1 ? TRUE : FALSE;
return S_OK;
}
private:
IDWriteFactory& factory;
ComSmartPtr<MemoryFontFileLoader> loader;
size_t rawDataIndex = std::numeric_limits<size_t>::max();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FontFileEnumerator)
};
class DirectWriteCustomFontCollectionLoader final : public ComBaseClassHelper<IDWriteFontCollectionLoader>
{
public:
explicit DirectWriteCustomFontCollectionLoader (IDWriteFactory& factoryIn)
: factory (factoryIn)
{
}
~DirectWriteCustomFontCollectionLoader() override
{
for (const auto& loader : fileLoaders)
factory.UnregisterFontFileLoader (loader);
}
Uuid addRawFontData (Span<const std::byte> blob)
{
const auto loader = becomeComSmartPtrOwner (new MemoryFontFileLoader { { blob.data(), blob.size() } });
factory.RegisterFontFileLoader (loader);
fileLoaders.push_back (loader);
return fileLoaders.back()->getUuid();
}
HRESULT WINAPI CreateEnumeratorFromKey (IDWriteFactory* factoryIn,
const void* collectionKey,
UINT32 collectionKeySize,
IDWriteFontFileEnumerator** fontFileEnumerator) noexcept override
{
if (collectionKeySize != Uuid::size())
return E_INVALIDARG;
const Uuid requestedCollectionKey { static_cast<const uint8*> (collectionKey) };
for (const auto& loader : fileLoaders)
{
if (loader->getUuid() != requestedCollectionKey)
continue;
*fontFileEnumerator = new FontFileEnumerator { *factoryIn, loader };
return S_OK;
}
return E_INVALIDARG;
}
private:
IDWriteFactory& factory;
std::vector<ComSmartPtr<MemoryFontFileLoader>> fileLoaders;
};
//==============================================================================
class Direct2DFactories
{
public:
Direct2DFactories()
{
ComSmartPtr<IDWriteFontCollection> collection;
if (SUCCEEDED (directWriteFactory->GetSystemFontCollection (collection.resetAndGetPointerAddress(), FALSE)) && collection != nullptr)
fonts.emplace (collection);
else
jassertfalse;
}
~Direct2DFactories()
{
if (directWriteFactory == nullptr)
return;
directWriteFactory->UnregisterFontCollectionLoader (collectionLoader);
}
[[nodiscard]] ComSmartPtr<IDWriteFactory> getDWriteFactory() const { return directWriteFactory; }
[[nodiscard]] ComSmartPtr<IDWriteFactory4> getDWriteFactory4() const { return directWriteFactory4; }
[[nodiscard]] AggregateFontCollection& getFonts() { jassert (fonts.has_value()); return *fonts; }
[[nodiscard]] ComSmartPtr<DirectWriteCustomFontCollectionLoader> getCollectionLoader() const { return collectionLoader; }
private:
DynamicLibrary direct2dDll { "d2d1.dll" }, directWriteDll { "DWrite.dll" };
const ComSmartPtr<ID2D1Factory> d2dFactory = [&]() -> ComSmartPtr<ID2D1Factory>
{
JUCE_LOAD_WINAPI_FUNCTION (direct2dDll,
D2D1CreateFactory,
d2d1CreateFactory,
HRESULT,
(D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**))
if (d2d1CreateFactory == nullptr)
return {};
D2D1_FACTORY_OPTIONS options;
options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
ComSmartPtr<ID2D1Factory> result;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof (ID2D1Factory),
&options,
(void**) result.resetAndGetPointerAddress());
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return result;
}();
const ComSmartPtr<IDWriteFactory> directWriteFactory = [&]() -> ComSmartPtr<IDWriteFactory>
{
JUCE_LOAD_WINAPI_FUNCTION (directWriteDll,
DWriteCreateFactory,
dWriteCreateFactory,
HRESULT,
(DWRITE_FACTORY_TYPE, REFIID, IUnknown**))
if (dWriteCreateFactory == nullptr)
return {};
ComSmartPtr<IDWriteFactory> result;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
__uuidof (IDWriteFactory),
(IUnknown**) result.resetAndGetPointerAddress());
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return result;
}();
const ComSmartPtr<DirectWriteCustomFontCollectionLoader> collectionLoader = [&]() -> ComSmartPtr<DirectWriteCustomFontCollectionLoader>
{
auto result = becomeComSmartPtrOwner (new DirectWriteCustomFontCollectionLoader { *directWriteFactory });
directWriteFactory->RegisterFontCollectionLoader (result);
return result;
}();
const ComSmartPtr<IDWriteFactory4> directWriteFactory4 = directWriteFactory.getInterface<IDWriteFactory4>();
std::optional<AggregateFontCollection> fonts;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories)
};
StringArray Font::findAllTypefaceNames()
{
SharedResourcePointer<Direct2DFactories> factories;
return factories->getFonts().findAllTypefaceNames();
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
if (FontStyleHelpers::isPlaceholderFamilyName (family))
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
SharedResourcePointer<Direct2DFactories> factories;
return factories->getFonts().findAllTypefaceStyles (family);
}
extern bool juce_isRunningInWine();
class WindowsDirectWriteTypeface final : public Typeface
{
public:
~WindowsDirectWriteTypeface() override
{
if (collection != nullptr)
factories->getFonts().removeCollection (collection);
}
static Typeface::Ptr from (const Font& f)
{
const auto name = f.getTypefaceName();
const auto style = f.getTypefaceStyle();
SharedResourcePointer<Direct2DFactories> factories;
const auto family = factories->getFonts().getFamilyByName (name.toWideCharPointer());
if (family == nullptr)
return getLastResortTypeface (f);
// Try matching the typeface style first
const auto fonts = AggregateFontCollection::getAllFontsInFamily (family);
const auto matchingStyle = std::find_if (fonts.begin(), fonts.end(), [&] (const auto& ptr)
{
return style.compareIgnoreCase (getFontFaceName (ptr)) == 0;
});
if (matchingStyle != fonts.end())
return fromFont (*matchingStyle, nullptr, &f, MetricsMechanism::dwriteOnly);
// No matching typeface style, so let dwrite try to find a reasonable substitute
const auto weight = f.isBold() ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL;
const auto italic = f.isItalic() ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
ComSmartPtr<IDWriteFont> dwFont;
if (FAILED (family->GetFirstMatchingFont (weight, DWRITE_FONT_STRETCH_NORMAL, italic, dwFont.resetAndGetPointerAddress())) || dwFont == nullptr)
return {};
return fromFont (dwFont, nullptr, &f, MetricsMechanism::dwriteOnly);
}
static Typeface::Ptr from (Span<const std::byte> blob)
{
SharedResourcePointer<Direct2DFactories> factories;
const auto dwFactory = factories->getDWriteFactory();
if (dwFactory == nullptr)
return {};
const auto collectionLoader = factories->getCollectionLoader();
const auto collectionKey = collectionLoader->addRawFontData ({ blob.data(), blob.size() });
ComSmartPtr<IDWriteFontCollection> customFontCollection;
if (FAILED (dwFactory->CreateCustomFontCollection (factories->getCollectionLoader(),
collectionKey.getRawData(),
(UINT32) collectionKey.size(),
customFontCollection.resetAndGetPointerAddress())))
return {};
if (customFontCollection == nullptr)
return {};
ComSmartPtr<IDWriteFontFamily> fontFamily;
if (FAILED (customFontCollection->GetFontFamily (0, fontFamily.resetAndGetPointerAddress())) || fontFamily == nullptr)
return {};
ComSmartPtr<IDWriteFont> dwFont;
if (FAILED (fontFamily->GetFont (0, dwFont.resetAndGetPointerAddress())) || dwFont == nullptr)
return {};
return fromFont (dwFont, customFontCollection, nullptr, MetricsMechanism::gdiWithDwriteFallback);
}
Native getNativeDetails() const override
{
return Native { hbFont.get(), nonPortableMetrics };
}
Typeface::Ptr createSystemFallback (const String& c, const String& language) const override
{
auto factory = factories->getDWriteFactory().getInterface<IDWriteFactory2>();
if (factory == nullptr)
{
// System font fallback is unavailable before Windows 8.1
jassertfalse;
return {};
}
ComSmartPtr<IDWriteFontFallback> fallback;
if (FAILED (factory->GetSystemFontFallback (fallback.resetAndGetPointerAddress())) || fallback == nullptr)
return {};
auto analysisSource = becomeComSmartPtrOwner (new AnalysisSource (c, language));
const auto originalName = getLocalisedFamilyName (*dwFont);
const auto mapped = factories->getFonts().mapCharacters (fallback,
analysisSource,
0,
numUtf16Words (c.toUTF16()),
originalName.toWideCharPointer(),
dwFont->GetWeight(),
dwFont->GetStyle(),
dwFont->GetStretch());
if (mapped.font == nullptr)
return {};
return fromFont (mapped.font, nullptr, nullptr, MetricsMechanism::dwriteOnly);
}
ComSmartPtr<IDWriteFontFace> getIDWriteFontFace() const { return dwFontFace; }
static Typeface::Ptr findSystemTypeface()
{
NONCLIENTMETRICS nonClientMetrics{};
nonClientMetrics.cbSize = sizeof (NONCLIENTMETRICS);
if (! SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICS), &nonClientMetrics, sizeof (NONCLIENTMETRICS)))
return {};
SharedResourcePointer<Direct2DFactories> factories;
ComSmartPtr<IDWriteGdiInterop> interop;
if (FAILED (factories->getDWriteFactory()->GetGdiInterop (interop.resetAndGetPointerAddress())) || interop == nullptr)
return {};
ComSmartPtr<IDWriteFont> dwFont;
if (FAILED (interop->CreateFontFromLOGFONT (&nonClientMetrics.lfMessageFont, dwFont.resetAndGetPointerAddress())) || dwFont == nullptr)
return {};
return fromFont (dwFont, nullptr, nullptr, MetricsMechanism::gdiWithDwriteFallback);
}
private:
static UINT32 numUtf16Words (const CharPointer_UTF16& str)
{
return (UINT32) (str.findTerminatingNull().getAddress() - str.getAddress());
}
class AnalysisSource final : public ComBaseClassHelper<IDWriteTextAnalysisSource>
{
public:
AnalysisSource (String cIn, String langIn)
: character (cIn), language (langIn) {}
~AnalysisSource() override = default;
JUCE_COMCALL GetLocaleName (UINT32, UINT32*, const WCHAR** localeName) noexcept override
{
*localeName = language.isNotEmpty() ? language.toWideCharPointer() : nullptr;
return S_OK;
}
JUCE_COMCALL GetNumberSubstitution (UINT32, UINT32*, IDWriteNumberSubstitution** substitution) noexcept override
{
*substitution = nullptr;
return S_OK;
}
DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept override
{
return DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
}
JUCE_COMCALL GetTextAtPosition (UINT32 textPosition, const WCHAR** textString, UINT32* textLength) noexcept override
{
if (textPosition == 0)
{
const auto utf16 = character.toUTF16();
*textString = utf16.getAddress();
*textLength = numUtf16Words (utf16);
}
else
{
// We don't expect this to be hit. If you see this, alert the JUCE team!
jassertfalse;
*textString = nullptr;
*textLength = 0;
}
return S_OK;
}
JUCE_COMCALL GetTextBeforePosition (UINT32, const WCHAR** textString, UINT32* textLength) noexcept override
{
// We don't expect this to be hit. If you see this, alert the JUCE team!
jassertfalse;
*textString = nullptr;
*textLength = 0;
return S_OK;
}
private:
String character;
String language;
};
static String getLocalisedFamilyName (IDWriteFont& font)
{
ComSmartPtr<IDWriteFontFamily> family;
if (FAILED (font.GetFontFamily (family.resetAndGetPointerAddress())) || family == nullptr)
return {};
return getLocalisedFamilyName (*family);
}
static String getLocalisedFamilyName (IDWriteFontFamily& fontFamily)
{
ComSmartPtr<IDWriteLocalizedStrings> familyNames;
if (FAILED (fontFamily.GetFamilyNames (familyNames.resetAndGetPointerAddress())) || familyNames == nullptr)
return {};
return getLocalisedName (familyNames);
}
static String getLocalisedStyle (IDWriteFont& font)
{
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
if (FAILED (font.GetFaceNames (faceNames.resetAndGetPointerAddress())) || faceNames == nullptr)
return {};
return getLocalisedName (faceNames);
}
WindowsDirectWriteTypeface (const String& name,
const String& style,
ComSmartPtr<IDWriteFont> font,
ComSmartPtr<IDWriteFontFace> face,
HbFont hbFontIn,
TypefaceAscentDescent metrics,
ComSmartPtr<IDWriteFontCollection> collectionIn = nullptr)
: Typeface (name, style),
collection (std::move (collectionIn)),
dwFont (font),
dwFontFace (face),
hbFont (std::move (hbFontIn)),
nonPortableMetrics (metrics)
{
if (collection != nullptr)
factories->getFonts().addCollection (collection);
}
static TypefaceAscentDescent getDwriteMetrics (IDWriteFontFace& face)
{
DWRITE_FONT_METRICS dwriteFontMetrics{};
face.GetMetrics (&dwriteFontMetrics);
return TypefaceAscentDescent { (float) dwriteFontMetrics.ascent / (float) dwriteFontMetrics.designUnitsPerEm,
(float) dwriteFontMetrics.descent / (float) dwriteFontMetrics.designUnitsPerEm };
}
static std::optional<TypefaceAscentDescent> getGdiMetrics (hb_font_t* font)
{
hb_position_t ascent{}, descent{};
if (! hb_ot_metrics_get_position (font, HB_OT_METRICS_TAG_HORIZONTAL_CLIPPING_ASCENT, &ascent) ||
! hb_ot_metrics_get_position (font, HB_OT_METRICS_TAG_HORIZONTAL_CLIPPING_DESCENT, &descent))
return {};
const auto upem = (float) hb_face_get_upem (hb_font_get_face (font));
return TypefaceAscentDescent { (float) std::abs (ascent) / upem, (float) std::abs (descent) / upem };
}
enum class MetricsMechanism
{
dwriteOnly,
gdiWithDwriteFallback,
};
static Typeface::Ptr fromFont (ComSmartPtr<IDWriteFont> dwFont,
ComSmartPtr<IDWriteFontCollection> collection,
const Font* fontForSynthetics,
MetricsMechanism mm)
{
ComSmartPtr<IDWriteFontFace> dwFace;
if (FAILED (dwFont->CreateFontFace (dwFace.resetAndGetPointerAddress())) || dwFace == nullptr)
return {};
const auto name = getLocalisedFamilyName (*dwFont);
const auto style = getLocalisedStyle (*dwFont);
const HbFace hbFace { hb_directwrite_face_create (dwFace) };
HbFont font { hb_font_create (hbFace.get()) };
const auto dwMetrics = getDwriteMetrics (*dwFace);
const auto metrics = mm == MetricsMechanism::gdiWithDwriteFallback
? getGdiMetrics (font.get()).value_or (dwMetrics)
: dwMetrics;
if (fontForSynthetics != nullptr)
FontStyleHelpers::initSynthetics (font.get(), *fontForSynthetics);
return new WindowsDirectWriteTypeface (name,
style,
dwFont,
dwFace,
std::move (font),
metrics,
collection);
}
// This attempts to replicate the behaviour of the non-directwrite typeface lookup in JUCE 7 and older
static Typeface::Ptr getLastResortTypeface (const Font& font)
{
auto* dc = CreateCompatibleDC (nullptr);
const ScopeGuard deleteDC { [&] { DeleteDC (dc); } };
SetMapperFlags (dc, 0);
SetMapMode (dc, MM_TEXT);
const auto style = font.getTypefaceStyle();
LOGFONTW lf{};
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
lf.lfQuality = PROOF_QUALITY;
lf.lfItalic = (BYTE) (style.contains ("Italic") ? TRUE : FALSE);
lf.lfWeight = style.contains ("Bold") ? FW_BOLD : FW_NORMAL;
lf.lfHeight = -256;
font.getTypefaceName().copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName));
auto* hfont = CreateFontIndirectW (&lf);
const ScopeGuard deleteFont { [&] { DeleteObject (hfont); } };
auto* prevFont = hfont != nullptr ? SelectObject (dc, hfont) : nullptr;
const ScopeGuard reinstateFont { [&] { if (prevFont != nullptr) SelectObject (dc, prevFont); } };
SharedResourcePointer<Direct2DFactories> factories;
ComSmartPtr<IDWriteGdiInterop> interop;
if (FAILED (factories->getDWriteFactory()->GetGdiInterop (interop.resetAndGetPointerAddress())) || interop == nullptr)
return {};
ComSmartPtr<IDWriteFontFace> dwFontFace;
if (FAILED (interop->CreateFontFaceFromHdc (dc, dwFontFace.resetAndGetPointerAddress())) || dwFontFace == nullptr)
return {};
const auto dwFont = factories->getFonts().findFontForFace (dwFontFace);
if (dwFont == nullptr)
return {};
return fromFont (dwFont, nullptr, nullptr, MetricsMechanism::gdiWithDwriteFallback);
}
SharedResourcePointer<Direct2DFactories> factories;
ComSmartPtr<IDWriteFontCollection> collection;
ComSmartPtr<IDWriteFont> dwFont;
ComSmartPtr<IDWriteFontFace> dwFontFace;
HbFont hbFont;
TypefaceAscentDescent nonPortableMetrics;
};
struct DefaultFontNames
{
DefaultFontNames()
{
if (juce_isRunningInWine())
{
// If we're running in Wine, then use fonts that might be available on Linux.
defaultSans = "Bitstream Vera Sans";
defaultSerif = "Bitstream Vera Serif";
defaultFixed = "Bitstream Vera Sans Mono";
}
else
{
defaultSans = "Verdana";
defaultSerif = "Times New Roman";
defaultFixed = "Lucida Console";
defaultFallback = "Tahoma"; // (contains plenty of unicode characters)
}
}
String defaultSans, defaultSerif, defaultFixed, defaultFallback;
};
Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font)
{
static DefaultFontNames defaultNames;
Font newFont (font);
auto faceName = font.getTypefaceName();
if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
if (font.getTypefaceStyle() == getDefaultStyle())
newFont.setTypefaceStyle ("Regular");
return Typeface::createSystemTypefaceFor (newFont);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return WindowsDirectWriteTypeface::from (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (Span<const std::byte> data)
{
return WindowsDirectWriteTypeface::from (data);
}
Typeface::Ptr Typeface::findSystemTypeface()
{
return WindowsDirectWriteTypeface::findSystemTypeface();
}
void Typeface::scanFolderForFonts (const File&)
{
// TODO(reuk)
}
} // namespace juce
@@ -0,0 +1,538 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
constexpr auto enableDirectXDebugLayer = false;
struct DxgiAdapter : public ReferenceCountedObject
{
using Ptr = ReferenceCountedObjectPtr<DxgiAdapter>;
static Ptr create (ComSmartPtr<ID2D1Factory2> d2dFactory, ComSmartPtr<IDXGIAdapter1> dxgiAdapterIn)
{
if (dxgiAdapterIn == nullptr || d2dFactory == nullptr)
return {};
Ptr result = new DxgiAdapter;
result->dxgiAdapter = dxgiAdapterIn;
for (UINT i = 0;; ++i)
{
ComSmartPtr<IDXGIOutput> output;
const auto hr = result->dxgiAdapter->EnumOutputs (i, output.resetAndGetPointerAddress());
if (hr == DXGI_ERROR_NOT_FOUND || hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
break;
result->dxgiOutputs.push_back (output);
}
// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
const auto creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT
| (enableDirectXDebugLayer ? D3D11_CREATE_DEVICE_DEBUG : 0);
if (const auto hr = D3D11CreateDevice (result->dxgiAdapter,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
creationFlags,
nullptr,
0,
D3D11_SDK_VERSION,
result->direct3DDevice.resetAndGetPointerAddress(),
nullptr,
nullptr); FAILED (hr))
{
return {};
}
if (const auto hr = result->direct3DDevice->QueryInterface (result->dxgiDevice.resetAndGetPointerAddress()); FAILED (hr))
return {};
if (const auto hr = d2dFactory->CreateDevice (result->dxgiDevice, result->direct2DDevice.resetAndGetPointerAddress()); FAILED (hr))
return {};
return result;
}
bool uniqueIDMatches (ReferenceCountedObjectPtr<DxgiAdapter> other) const
{
if (other == nullptr)
return false;
auto luid = getAdapterUniqueID();
auto otherLuid = other->getAdapterUniqueID();
return (luid.HighPart == otherLuid.HighPart) && (luid.LowPart == otherLuid.LowPart);
}
LUID getAdapterUniqueID() const
{
DXGI_ADAPTER_DESC1 desc;
if (auto hr = dxgiAdapter->GetDesc1 (&desc); SUCCEEDED (hr))
return desc.AdapterLuid;
return LUID { 0, 0 };
}
ComSmartPtr<ID3D11Device> direct3DDevice;
ComSmartPtr<IDXGIDevice> dxgiDevice;
ComSmartPtr<ID2D1Device1> direct2DDevice;
ComSmartPtr<IDXGIAdapter1> dxgiAdapter;
std::vector<ComSmartPtr<IDXGIOutput>> dxgiOutputs;
private:
DxgiAdapter() = default;
};
struct DxgiAdapterListener
{
virtual ~DxgiAdapterListener() = default;
virtual void adapterCreated (DxgiAdapter::Ptr adapter) = 0;
virtual void adapterRemoved (DxgiAdapter::Ptr adapter) = 0;
};
class DxgiAdapters
{
public:
explicit DxgiAdapters (ComSmartPtr<ID2D1Factory2> d2dFactoryIn)
: d2dFactory (d2dFactoryIn)
{
updateAdapters();
}
~DxgiAdapters()
{
releaseAdapters();
}
void updateAdapters()
{
if (factory != nullptr && factory->IsCurrent() && ! adapterArray.isEmpty())
return;
releaseAdapters();
if (factory == nullptr || ! factory->IsCurrent())
factory = makeDxgiFactory();
if (factory == nullptr)
{
// If you hit this, we were unable to create a DXGI Factory, so we won't be able to
// render anything using Direct2D.
// Maybe this version of Windows doesn't have Direct2D support.
jassertfalse;
return;
}
for (UINT i = 0;; ++i)
{
ComSmartPtr<IDXGIAdapter1> dxgiAdapter;
if (factory->EnumAdapters1 (i, dxgiAdapter.resetAndGetPointerAddress()) == DXGI_ERROR_NOT_FOUND)
break;
if (const auto adapter = DxgiAdapter::create (d2dFactory, dxgiAdapter))
{
adapterArray.add (adapter);
listeners.call ([adapter] (DxgiAdapterListener& l) { l.adapterCreated (adapter); });
}
}
}
void releaseAdapters()
{
for (const auto& adapter : adapterArray)
listeners.call ([adapter] (DxgiAdapterListener& l) { l.adapterRemoved (adapter); });
adapterArray.clear();
}
const auto& getAdapterArray() const
{
return adapterArray;
}
auto getFactory() const
{
return factory;
}
DxgiAdapter::Ptr getAdapterForHwnd (HWND hwnd) const
{
const auto monitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONULL);
if (monitor == nullptr)
return getDefaultAdapter();
for (auto& adapter : adapterArray)
{
for (const auto& dxgiOutput : adapter->dxgiOutputs)
{
DXGI_OUTPUT_DESC desc{};
if (FAILED (dxgiOutput->GetDesc (&desc)))
continue;
if (desc.Monitor == monitor)
return adapter;
}
}
return getDefaultAdapter();
}
DxgiAdapter::Ptr getDefaultAdapter() const
{
return adapterArray.getFirst();
}
void addListener (DxgiAdapterListener& l)
{
listeners.add (&l);
}
void removeListener (DxgiAdapterListener& l)
{
listeners.remove (&l);
}
private:
static ComSmartPtr<IDXGIFactory2> makeDxgiFactory()
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
ComSmartPtr<IDXGIFactory2> result;
if (const auto hr = CreateDXGIFactory2 (0, __uuidof (IDXGIFactory2), (void**) result.resetAndGetPointerAddress()); SUCCEEDED (hr))
return result;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
// If CreateDXGIFactory fails, check to see if this is being called in the context of DllMain.
// CreateDXGIFactory will always fail if called from the context of DllMain. In this case, the renderer
// will create a software image instead as a fallback, but that won't perform as well.
//
// You may be creating an Image as a static object, which will likely be created in the context of DllMain.
// Consider deferring your Image creation until later.
jassertfalse;
return {};
}
ComSmartPtr<ID2D1Factory2> d2dFactory;
// It's possible that we'll need to add/remove listeners from background threads, especially in
// the case that Images are created on a background thread.
ThreadSafeListenerList<DxgiAdapterListener> listeners;
ComSmartPtr<IDXGIFactory2> factory = makeDxgiFactory();
ReferenceCountedArray<DxgiAdapter> adapterArray;
};
class DirectX
{
public:
DirectX() = default;
auto getD2DFactory() const { return d2dSharedFactory; }
private:
ComSmartPtr<ID2D1Factory2> d2dSharedFactory = [&]
{
D2D1_FACTORY_OPTIONS options;
options.debugLevel = enableDirectXDebugLayer ? D2D1_DEBUG_LEVEL_INFORMATION : D2D1_DEBUG_LEVEL_NONE;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
ComSmartPtr<ID2D1Factory2> result;
auto hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED,
__uuidof (ID2D1Factory2),
&options,
(void**) result.resetAndGetPointerAddress());
jassertquiet (SUCCEEDED (hr));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return result;
}();
public:
DxgiAdapters adapters { d2dSharedFactory };
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectX)
};
struct D2DUtilities
{
template <typename Type>
static D2D1_RECT_F toRECT_F (const Rectangle<Type>& r)
{
return { (float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom() };
}
template <typename Type>
static D2D1_RECT_U toRECT_U (const Rectangle<Type>& r)
{
return { (UINT32) r.getX(), (UINT32) r.getY(), (UINT32) r.getRight(), (UINT32) r.getBottom() };
}
template <typename Type>
static RECT toRECT (const Rectangle<Type>& r)
{
return { r.getX(), r.getY(), r.getRight(), r.getBottom() };
}
static Rectangle<int> toRectangle (const RECT& r)
{
return Rectangle<int>::leftTopRightBottom (r.left, r.top, r.right, r.bottom);
}
static Point<int> toPoint (POINT p) noexcept { return { p.x, p.y }; }
static POINT toPOINT (Point<int> p) noexcept { return { p.x, p.y }; }
static D2D1_POINT_2U toPOINT_2U (Point<int> p) { return D2D1::Point2U ((UINT32) p.x, (UINT32) p.y); }
static D2D1_COLOR_F toCOLOR_F (Colour c)
{
return { c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha() };
}
static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& t)
{
return { t.mat00, t.mat10, t.mat01, t.mat11, t.mat02, t.mat12 };
}
static AffineTransform matrixToTransform (const D2D1_MATRIX_3X2_F& m)
{
return { m._11, m._21, m._31, m._12, m._22, m._32 };
}
static Rectangle<int> rectFromSize (D2D1_SIZE_U s)
{
return { (int) s.width, (int) s.height };
}
static ComSmartPtr<ID2D1Device1> getDeviceForContext (ComSmartPtr<ID2D1DeviceContext1> context)
{
if (context == nullptr)
return {};
ComSmartPtr<ID2D1Device> device;
context->GetDevice (device.resetAndGetPointerAddress());
return device.getInterface<ID2D1Device1>();
}
};
//==============================================================================
struct Direct2DDeviceContext
{
static ComSmartPtr<ID2D1DeviceContext1> create (ComSmartPtr<ID2D1Device1> device)
{
ComSmartPtr<ID2D1DeviceContext1> result;
if (const auto hr = device->CreateDeviceContext (D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
result.resetAndGetPointerAddress());
FAILED (hr))
{
jassertfalse;
return {};
}
result->SetTextAntialiasMode (D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
result->SetAntialiasMode (D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
result->SetUnitMode (D2D1_UNIT_MODE_PIXELS);
return result;
}
static ComSmartPtr<ID2D1DeviceContext1> create (DxgiAdapter::Ptr adapter)
{
return adapter != nullptr ? create (adapter->direct2DDevice) : nullptr;
}
Direct2DDeviceContext() = delete;
};
//==============================================================================
struct Direct2DBitmap
{
Direct2DBitmap() = delete;
static ComSmartPtr<ID2D1Bitmap1> toBitmap (const Image& image,
ComSmartPtr<ID2D1DeviceContext1> deviceContext,
Image::PixelFormat outputFormat)
{
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (Direct2DMetricsHub::getInstance()->imageContextMetrics, createBitmapTime);
jassert (outputFormat == Image::ARGB || outputFormat == Image::SingleChannel);
JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::createDirect2DBitmapFromImage, etw::graphicsKeyword);
// Calling Image::convertedToFormat could cause unchecked recursion since convertedToFormat
// calls Graphics::drawImageAt which calls Direct2DGraphicsContext::drawImage which calls this function...
//
// Use a software image for the conversion instead so the Graphics::drawImageAt call doesn't go
// through the Direct2D renderer
//
// Be sure to explicitly set the DPI to 96.0 for the image; otherwise it will default to the screen DPI
// and may be scaled incorrectly
const auto convertedImage = SoftwareImageType{}.convert (image).convertedToFormat (outputFormat);
if (! convertedImage.isValid())
return {};
Image::BitmapData bitmapData { convertedImage, Image::BitmapData::readWrite };
D2D1_BITMAP_PROPERTIES1 bitmapProperties{};
bitmapProperties.pixelFormat.format = outputFormat == Image::SingleChannel
? DXGI_FORMAT_A8_UNORM
: DXGI_FORMAT_B8G8R8A8_UNORM;
bitmapProperties.pixelFormat.alphaMode = outputFormat == Image::RGB
? D2D1_ALPHA_MODE_IGNORE
: D2D1_ALPHA_MODE_PREMULTIPLIED;
bitmapProperties.dpiX = USER_DEFAULT_SCREEN_DPI;
bitmapProperties.dpiY = USER_DEFAULT_SCREEN_DPI;
const D2D1_SIZE_U size { (UINT32) image.getWidth(), (UINT32) image.getHeight() };
ComSmartPtr<ID2D1Bitmap1> bitmap;
deviceContext->CreateBitmap (size,
bitmapData.data,
(UINT32) bitmapData.lineStride,
bitmapProperties,
bitmap.resetAndGetPointerAddress());
return bitmap;
}
static ComSmartPtr<ID2D1Bitmap1> createBitmap (ComSmartPtr<ID2D1DeviceContext1> deviceContext,
Image::PixelFormat format,
D2D_SIZE_U size,
D2D1_BITMAP_OPTIONS options)
{
JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::createDirect2DBitmap, etw::graphicsKeyword);
JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (Direct2DMetricsHub::getInstance()->imageContextMetrics, createBitmapTime);
// Verify that the GPU can handle a bitmap of this size
//
// If you need a bitmap larger than this, you'll need to either split it up into multiple bitmaps
// or use a software image (see SoftwareImageType).
const auto maxBitmapSize = deviceContext->GetMaximumBitmapSize();
jassertquiet (size.width <= maxBitmapSize && size.height <= maxBitmapSize);
const auto pixelFormat = D2D1::PixelFormat (format == Image::SingleChannel
? DXGI_FORMAT_A8_UNORM
: DXGI_FORMAT_B8G8R8A8_UNORM,
format == Image::RGB
? D2D1_ALPHA_MODE_IGNORE
: D2D1_ALPHA_MODE_PREMULTIPLIED);
const auto bitmapProperties = D2D1::BitmapProperties1 (options, pixelFormat);
ComSmartPtr<ID2D1Bitmap1> bitmap;
const auto hr = deviceContext->CreateBitmap (size,
{},
{},
bitmapProperties,
bitmap.resetAndGetPointerAddress());
jassertquiet (SUCCEEDED (hr) && bitmap != nullptr);
return bitmap;
}
};
//==============================================================================
/* UpdateRegion extracts the invalid region for a window
UpdateRegion is used to service WM_PAINT to add the invalid region of a window to
deferredRepaints. UpdateRegion marks the region as valid, and the region should be painted on the
next vblank.
This is similar to the invalid region update in HWNDComponentPeer::handlePaintMessage()
*/
class UpdateRegion
{
public:
void findRECTAndValidate (HWND windowHandle)
{
numRect = 0;
auto regionHandle = CreateRectRgn (0, 0, 0, 0);
if (regionHandle == nullptr)
{
ValidateRect (windowHandle, nullptr);
return;
}
auto regionType = GetUpdateRgn (windowHandle, regionHandle, false);
if (regionType == SIMPLEREGION || regionType == COMPLEXREGION)
{
auto regionDataBytes = GetRegionData (regionHandle, (DWORD) block.getSize(), (RGNDATA*) block.getData());
if (regionDataBytes > block.getSize())
{
block.ensureSize (regionDataBytes);
regionDataBytes = GetRegionData (regionHandle, (DWORD) block.getSize(), (RGNDATA*) block.getData());
}
if (regionDataBytes > 0)
{
auto header = (RGNDATAHEADER const* const) block.getData();
if (header->iType == RDH_RECTANGLES)
numRect = header->nCount;
}
}
if (numRect > 0)
ValidateRgn (windowHandle, regionHandle);
else
ValidateRect (windowHandle, nullptr);
DeleteObject (regionHandle);
}
void clear()
{
numRect = 0;
}
Span<const RECT> getRects() const
{
auto header = (RGNDATAHEADER const* const) block.getData();
auto* data = (RECT*) (header + 1);
return { data, numRect };
}
private:
MemoryBlock block { 1024 };
uint32 numRect = 0;
};
} // namespace juce
@@ -0,0 +1,310 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
namespace juce::etw
{
//==============================================================================
/*
The following XML can be passed to Windows Performance Recorder (WPR) to enable tracing from
JUCE projects.
- Save the XML into a file with the name JUCE.wprp
- Run the following command from an admin command prompt to start capture. If you use a Developer
Command Prompt, wpr.exe should be found on the PATH already.
- <path to wpr>\wpr.exe -start JUCE.wprp
- Start your JUCE project
- Run the following command from an admin command prompt to stop capture
- <path to wpr>\wpr.exe -stop TraceCaptureFile.etl description
<?xml version="1.0" encoding="utf-8"?>
<WindowsPerformanceRecorder Version="1.0" Author="Microsoft Corporation" Copyright="Microsoft Corporation" Company="Microsoft Corporation">
<Profiles>
<EventCollector Id="EventCollector_JUCETraceLogProvider" Name="JUCETraceLogProvider">
<BufferSize Value="64" />
<Buffers Value="4" />
</EventCollector>
<EventProvider Id="EventProvider_JUCETraceLogProvider" Name="6A612E78-284D-4DDB-877A-5F521EB33132" />
<Profile Id="JUCETraceLogProvider.Verbose.File" Name="JUCETraceLogProvider" Description="JUCETraceLogProvider" LoggingMode="File" DetailLevel="Verbose">
<Collectors>
<EventCollectorId Value="EventCollector_JUCETraceLogProvider">
<EventProviders>
<EventProviderId Value="EventProvider_JUCETraceLogProvider" />
</EventProviders>
</EventCollectorId>
</Collectors>
</Profile>
<Profile Id="JUCETraceLogProvider.Light.File" Name="JUCETraceLogProvider" Description="JUCETraceLogProvider" Base="JUCETraceLogProvider.Verbose.File" LoggingMode="File" DetailLevel="Light" />
<Profile Id="JUCETraceLogProvider.Verbose.Memory" Name="JUCETraceLogProvider" Description="JUCETraceLogProvider" Base="JUCETraceLogProvider.Verbose.File" LoggingMode="Memory" DetailLevel="Verbose" />
<Profile Id="JUCETraceLogProvider.Light.Memory" Name="JUCETraceLogProvider" Description="JUCETraceLogProvider" Base="JUCETraceLogProvider.Verbose.File" LoggingMode="Memory" DetailLevel="Light" />
</Profiles>
</WindowsPerformanceRecorder>
*/
//==============================================================================
enum
{
paintKeyword = 1 << 0,
sizeKeyword = 1 << 1,
graphicsKeyword = 1 << 2,
crucialKeyword = 1 << 3,
threadPaintKeyword = 1 << 4,
messageKeyword = 1 << 5,
direct2dKeyword = 1 << 6,
softwareRendererKeyword = 1 << 7,
resourcesKeyword = 1 << 8,
componentKeyword = 1 << 9,
spriteKeyword = 1 << 10
};
enum : uint64_t
{
direct2dHwndPaintStart,
direct2dHwndPaintEnd,
endDraw,
present1SwapChainStart,
present1SwapChainEnd,
swapChainThreadEvent,
waitForVBlankDone,
callVBlankListeners,
resize,
createResource,
presentIdleFrame,
direct2dImagePaintStart,
direct2dImagePaintEnd,
startD2DFrame,
flush,
startGDIFrame,
startGDIImage,
endGDIFrame,
createLowLevelGraphicsContext,
createDeviceResources,
createSwapChain,
createSwapChainBuffer,
createPeer,
mapBitmap,
unmapBitmap,
createDirect2DBitmapFromImage,
createDirect2DBitmap,
setOrigin,
addTransform,
clipToRectangle,
clipToRectangleList,
excludeClipRectangle,
clipToPath,
clipToImageAlpha,
saveState,
restoreState,
beginTransparencyLayer,
endTransparencyLayer,
setFill,
setOpacity,
setInterpolationQuality,
fillRect,
fillRectReplace,
fillRectList,
drawRectTranslated,
drawRectTransformed,
drawRect,
fillPath,
strokePath,
drawPath,
drawImage,
drawLine,
setFont,
drawGlyph,
drawGlyphRun,
drawTextLayout,
drawRoundedRectangle,
fillRoundedRectangle,
drawEllipse,
fillEllipse,
filledGeometryRealizationCacheHit,
filledGeometryRealizationCreated,
strokedGeometryRealizationCacheHit,
strokedGeometryRealizationCreated,
releaseGeometryRealization,
gradientCacheHit,
gradientCreated,
releaseGradient,
nativeDropShadow,
nativeGlowEffect,
resetToDefaultState,
reduceClipRegionRectangle,
reduceClipRegionRectangleList,
reduceClipRegionImage,
reduceClipRegionPath,
excludeClipRegion,
fillAll,
repaint,
paintComponentAndChildren,
paintWithinParentContext,
createSpriteBatch,
setSprites,
addSprites,
drawSprites,
};
#if JUCE_WINDOWS && JUCE_ETW_TRACELOGGING
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wc++98-compat-extra-semi", "-Wmissing-prototypes", "-Wgnu-zero-variadic-macro-arguments")
TRACELOGGING_DECLARE_PROVIDER (JUCETraceLogProvider);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#define JUCE_WRITE_TRACE_LOG_VA(code, keyword, ...) \
TraceLoggingWrite (::juce::etw::JUCETraceLogProvider, \
#code, \
TraceLoggingLevel (TRACE_LEVEL_INFORMATION), \
TraceLoggingKeyword ((UINT64) keyword), \
__VA_ARGS__, \
TraceLoggingValue ((unsigned short) code, "code"))
#define JUCE_WRITE_TRACE_LOG(code, keyword) \
TraceLoggingWrite (::juce::etw::JUCETraceLogProvider, \
#code, \
TraceLoggingLevel (TRACE_LEVEL_INFORMATION), \
TraceLoggingKeyword ((UINT64) keyword), \
TraceLoggingValue ((unsigned short) code, "code"))
#else
#define JUCE_WRITE_TRACE_LOG_VA(code, keyword, ...)
#define JUCE_WRITE_TRACE_LOG(code, keyword)
#endif
template <typename Number>
auto toVector (const Rectangle<Number>& r)
{
return std::vector { r.getX(), r.getY(), r.getWidth(), r.getHeight() };
}
template <typename Number>
auto toVector (const RectangleList<Number>& list)
{
std::vector<Number> result;
for (const auto& r : list)
result.insert (result.end(), { r.getX(), r.getY(), r.getWidth(), r.getHeight() });
return result;
}
//==============================================================================
#if JUCE_WINDOWS && JUCE_ETW_TRACELOGGING
#define JUCE_SCOPED_TRACE_EVENT_FRAME(code, keyword, frameNumber) \
const ScopeGuard scopedEvent_ ## __LINE__ { [start = ::juce::Time::getHighResolutionTicks(), frame = (frameNumber)] \
{ \
const auto ticks = ::juce::Time::getHighResolutionTicks() - start; \
JUCE_WRITE_TRACE_LOG_VA (code, keyword, TraceLoggingValue (ticks), TraceLoggingValue (frame)); \
} };
#define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32(code, keyword, frameNumber, rectIn) \
const ScopeGuard scopedEvent_ ## __LINE__ { [start = ::juce::Time::getHighResolutionTicks(), frame = (frameNumber), rect = (rectIn)] \
{ \
const auto ticks = ::juce::Time::getHighResolutionTicks() - start; \
const std::vector<float> vec = ::juce::etw::toVector (rect); \
JUCE_WRITE_TRACE_LOG_VA (code, \
keyword, \
TraceLoggingValue (ticks), \
TraceLoggingValue (frame), \
TraceLoggingFloat32Array (vec.data(), (UINT16) vec.size(), "rect")); \
} };
#define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32(code, keyword, frameNumber, rectIn) \
const ScopeGuard scopedEvent_ ## __LINE__ { [start = ::juce::Time::getHighResolutionTicks(), frame = (frameNumber), rect = (rectIn)] \
{ \
const auto ticks = ::juce::Time::getHighResolutionTicks() - start; \
const std::vector<INT32> vec = ::juce::etw::toVector (rect); \
JUCE_WRITE_TRACE_LOG_VA (code, \
keyword, \
TraceLoggingValue (ticks), \
TraceLoggingValue (frame), \
TraceLoggingInt32Array (vec.data(), (UINT16) vec.size(), "rect")); \
} };
#else
#define JUCE_SCOPED_TRACE_EVENT_FRAME(code, keyword, frameNumber)
#define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32(code, keyword, frameNumber, rectIn)
#define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32(code, keyword, frameNumber, rectIn)
#endif
//==============================================================================
#define JUCE_TRACE_LOG_D2D_PAINT_CALL(code, frameNumber) \
JUCE_WRITE_TRACE_LOG_VA (code, etw::paintKeyword | etw::direct2dKeyword, TraceLoggingValue ((UINT64) frameNumber, "frame"))
#define JUCE_TRACE_LOG_JUCE_VBLANK_THREAD_EVENT \
JUCE_WRITE_TRACE_LOG (etw::waitForVBlankDone, etw::softwareRendererKeyword)
#define JUCE_TRACE_LOG_JUCE_VBLANK_CALL_LISTENERS \
JUCE_WRITE_TRACE_LOG (etw::callVBlankListeners, etw::softwareRendererKeyword)
#define JUCE_TRACE_LOG_D2D_RESIZE(message) \
JUCE_WRITE_TRACE_LOG_VA (etw::resize, etw::paintKeyword | etw::direct2dKeyword, TraceLoggingValue (message, "message"))
#define JUCE_TRACE_LOG_D2D_IMAGE_MAP_DATA \
JUCE_WRITE_TRACE_LOG (etw::mapBitmap, etw::direct2dKeyword)
#define JUCE_TRACE_LOG_D2D_IMAGE_UNMAP_DATA \
JUCE_WRITE_TRACE_LOG (etw::unmapBitmap, etw::direct2dKeyword)
#define JUCE_TRACE_LOG_PAINT_COMPONENT_AND_CHILDREN(depth) \
JUCE_WRITE_TRACE_LOG_VA (etw::paintComponentAndChildren, etw::paintKeyword, TraceLoggingValue (depth, "depth"))
#define JUCE_TRACE_LOG_PAINT_CALL(code, frameNumber) \
JUCE_WRITE_TRACE_LOG_VA (code, etw::softwareRendererKeyword, TraceLoggingValue ((UINT64) frameNumber, "frame"))
#if JUCE_ETW_TRACELOGGING
#define JUCE_TRACE_EVENT_INT_RECT_LIST(code, keyword, frameNumber, rect) \
{ \
const std::vector<INT32> vec = ::juce::etw::toVector (rect); \
JUCE_WRITE_TRACE_LOG (code, \
etw::softwareRendererKeyword, \
TraceLoggingValue ((UINT64) frameNumber, "frame"), \
TraceLoggingInt32Array (vec.data(), (UINT16) vec.size(), "rect")); \
}
#define JUCE_TRACE_EVENT_INT_RECT(code, keyword, rect) \
{ \
const std::vector<INT32> vec = ::juce::etw::toVector (rect); \
JUCE_WRITE_TRACE_LOG (code, \
etw::softwareRendererKeyword, \
TraceLoggingInt32Array (vec.data(), (UINT16) vec.size(), "rect")); \
}
#else
#define JUCE_TRACE_EVENT_INT_RECT_LIST(code, keyword, frameNumber, rect)
#define JUCE_TRACE_EVENT_INT_RECT(code, keyword, rect)
#endif
} // namespace juce::etw
@@ -0,0 +1,634 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font)
{
Font f (font);
f.setTypefaceName ([&]() -> String
{
const auto faceName = font.getTypefaceName();
if (faceName == Font::getDefaultSansSerifFontName()) return "Roboto";
if (faceName == Font::getDefaultSerifFontName()) return "Roboto";
if (faceName == Font::getDefaultMonospacedFontName()) return "Roboto";
return faceName;
}());
return Typeface::createSystemTypefaceFor (f);
}
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;")
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "()V") \
METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V")
DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "()V") \
FIELD (left, "left", "F") \
FIELD (right, "right", "F") \
FIELD (top, "top", "F") \
FIELD (bottom, "bottom", "F") \
METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V")
DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \
METHOD (update, "update", "([B)V") \
METHOD (digest, "digest", "()[B")
DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (open, "open", "(Ljava/lang/String;)Ljava/io/InputStream;") \
DECLARE_JNI_CLASS (AndroidAssetManager, "android/content/res/AssetManager")
#undef JNI_CLASS_MEMBERS
// Defined in juce_core
std::unique_ptr<InputStream> makeAndroidInputStreamWrapper (LocalRef<jobject> stream);
struct AndroidCachedTypeface
{
std::shared_ptr<hb_font_t> font;
TypefaceAscentDescent nonPortableMetrics;
};
//==============================================================================
class MemoryFontCache : public DeletedAtShutdown
{
public:
using Value = AndroidCachedTypeface;
~MemoryFontCache()
{
clearSingletonInstance();
}
struct Key
{
String name, style;
auto tie() const { return std::tuple (name, style); }
bool operator< (const Key& other) const { return tie() < other.tie(); }
bool operator== (const Key& other) const { return tie() == other.tie(); }
};
void add (const Key& key, const Value& value)
{
const std::scoped_lock lock { mutex };
cache.emplace (key, value);
}
void remove (const Key& p)
{
const std::scoped_lock lock { mutex };
cache.erase (p);
}
std::set<String> getAllNames() const
{
const std::scoped_lock lock { mutex };
std::set<String> result;
for (const auto& item : cache)
result.insert (item.first.name);
return result;
}
std::set<String> getStylesForFamily (const String& family) const
{
const std::scoped_lock lock { mutex };
const auto lower = std::lower_bound (cache.begin(), cache.end(), family, [] (const auto& a, const String& b)
{
return a.first.name < b;
});
const auto upper = std::upper_bound (cache.begin(), cache.end(), family, [] (const String& a, const auto& b)
{
return a < b.first.name;
});
std::set<String> result;
for (const auto& item : makeRange (lower, upper))
result.insert (item.first.style);
return result;
}
std::optional<Value> find (const Key& key) const
{
const std::scoped_lock lock { mutex };
const auto iter = cache.find (key);
if (iter != cache.end())
return iter->second;
return {};
}
JUCE_DECLARE_SINGLETON_INLINE (MemoryFontCache, true)
private:
std::map<Key, Value> cache;
mutable std::mutex mutex;
};
StringArray Font::findAllTypefaceNames()
{
auto results = [&]
{
if (auto* cache = MemoryFontCache::getInstance())
return cache->getAllNames();
return std::set<String>{};
}();
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
results.insert (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false));
StringArray s;
for (const auto& family : results)
s.add (family);
return s;
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
auto results = [&]
{
if (auto* cache = MemoryFontCache::getInstance())
return cache->getStylesForFamily (family);
return std::set<String>{};
}();
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf"))
results.insert (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false));
StringArray s;
for (const auto& style : results)
s.add (style);
return s;
}
//==============================================================================
class AndroidTypeface final : public Typeface
{
public:
enum class DoCache
{
no,
yes
};
static Typeface::Ptr from (const Font& font)
{
if (auto* cache = MemoryFontCache::getInstance())
if (auto result = cache->find ({ font.getTypefaceName(), font.getTypefaceStyle() }))
return new AndroidTypeface (DoCache::no, result->font, result->nonPortableMetrics, font.getTypefaceName(), font.getTypefaceStyle());
auto [blob, metrics] = getBlobForFont (font);
auto face = FontStyleHelpers::getFaceForBlob ({ static_cast<const char*> (blob.getData()), blob.getSize() }, 0);
if (face == nullptr)
{
jassertfalse;
return {};
}
HbFont hbFont { hb_font_create (face.get()) };
FontStyleHelpers::initSynthetics (hbFont.get(), font);
return new AndroidTypeface (DoCache::no, std::move (hbFont), metrics, font.getTypefaceName(), font.getTypefaceStyle());
}
static Typeface::Ptr from (Span<const std::byte> blob, unsigned int index = 0)
{
return fromMemory (DoCache::yes, blob, index);
}
Native getNativeDetails() const override
{
return Native { hbFont.get(), nonPortableMetrics };
}
Typeface::Ptr createSystemFallback (const String& text, const String& language) const override
{
if (__builtin_available (android 29, *))
return matchWithAFontMatcher (text, language);
// The font-fallback API is only available on Android API level 29+
jassertfalse;
return {};
}
~AndroidTypeface() override
{
if (doCache == DoCache::yes)
if (auto* c = MemoryFontCache::getInstance())
c->remove ({ getName(), getStyle() });
}
static Typeface::Ptr findSystemTypeface()
{
if (__builtin_available (android 29, *))
return findSystemTypefaceWithMatcher();
return from (FontOptions{}.withName ("Roboto"));
}
private:
static __INTRODUCED_IN (29) Typeface::Ptr fromMatchedFont (AFont* matched)
{
if (matched == nullptr)
{
// Unable to find any matching fonts. This should never happen - in the worst case,
// we should at least get a font with the tofu character.
jassertfalse;
return {};
}
const File matchedFile { AFont_getFontFilePath (matched) };
const auto matchedIndex = AFont_getCollectionIndex (matched);
auto* cache = TypefaceFileCache::getInstance();
if (cache == nullptr)
return {}; // Perhaps we're shutting down
return cache->get ({ matchedFile, (int) matchedIndex }, &loadCompatibleFont);
}
static __INTRODUCED_IN (29) Typeface::Ptr findSystemTypefaceWithMatcher()
{
using AFontMatcherPtr = std::unique_ptr<AFontMatcher, FunctionPointerDestructor<AFontMatcher_destroy>>;
using AFontPtr = std::unique_ptr<AFont, FunctionPointerDestructor<AFont_close>>;
constexpr uint16_t testString[] { 't', 'e', 's', 't' };
const AFontMatcherPtr matcher { AFontMatcher_create() };
const AFontPtr matched { AFontMatcher_match (matcher.get(),
"system-ui",
testString,
std::size (testString),
nullptr) };
return fromMatchedFont (matched.get());
}
__INTRODUCED_IN (29) Typeface::Ptr matchWithAFontMatcher (const String& text, const String& language) const
{
using AFontMatcherPtr = std::unique_ptr<AFontMatcher, FunctionPointerDestructor<AFontMatcher_destroy>>;
using AFontPtr = std::unique_ptr<AFont, FunctionPointerDestructor<AFont_close>>;
const AFontMatcherPtr matcher { AFontMatcher_create() };
const auto weight = hb_style_get_value (hbFont.get(), HB_STYLE_TAG_WEIGHT);
const auto italic = hb_style_get_value (hbFont.get(), HB_STYLE_TAG_ITALIC) != 0.0f;
AFontMatcher_setStyle (matcher.get(), (uint16_t) weight, italic);
AFontMatcher_setLocales (matcher.get(), language.toRawUTF8());
const auto utf16 = text.toUTF16();
const AFontPtr matched { AFontMatcher_match (matcher.get(),
readFontName (hb_font_get_face (hbFont.get()),
HB_OT_NAME_ID_FONT_FAMILY,
nullptr).toRawUTF8(),
unalignedPointerCast<const uint16_t*> (utf16.getAddress()),
(uint32_t) (utf16.findTerminatingNull().getAddress() - utf16.getAddress()),
nullptr) };
return fromMatchedFont (matched.get());
}
static Typeface::Ptr loadCompatibleFont (const TypefaceFileAndIndex& info)
{
FileInputStream stream { info.file };
if (! stream.openedOk())
return {};
MemoryBlock mb;
stream.readIntoMemoryBlock (mb);
auto result = fromMemory (DoCache::no,
{ static_cast<const std::byte*> (mb.getData()), mb.getSize() },
(unsigned int) info.index);
if (result == nullptr)
return {};
const auto tech = result->getColourGlyphFormats();
const auto hasSupportedColours = (tech & (colourGlyphFormatCOLRv0 | colourGlyphFormatBitmap)) != 0;
// If the font only uses unsupported colour technologies, assume it's the system emoji font
// and try to return a compatible version of the font
if (tech != 0 && ! hasSupportedColours)
if (auto fallback = from (FontOptions { "NotoColorEmojiLegacy", FontValues::defaultFontHeight, Font::plain }); fallback != nullptr)
return fallback;
return result;
}
static Typeface::Ptr fromMemory (DoCache cache, Span<const std::byte> blob, unsigned int index = 0)
{
auto face = FontStyleHelpers::getFaceForBlob ({ reinterpret_cast<const char*> (blob.data()), blob.size() }, index);
if (face == nullptr)
return {};
const auto metrics = findNonPortableMetricsForData (blob);
return new AndroidTypeface (cache,
HbFont { hb_font_create (face.get()) },
metrics,
readFontName (face.get(), HB_OT_NAME_ID_FONT_FAMILY, nullptr),
readFontName (face.get(), HB_OT_NAME_ID_FONT_SUBFAMILY, nullptr));
}
static String readFontName (hb_face_t* face, hb_ot_name_id_t nameId, hb_language_t language)
{
unsigned int textSize{};
textSize = hb_ot_name_get_utf8 (face, nameId, language, &textSize, nullptr);
std::vector<char> nameString (textSize + 1, 0);
textSize = (unsigned int) nameString.size();
hb_ot_name_get_utf8 (face, nameId, language, &textSize, nameString.data());
return nameString.data();
}
AndroidTypeface (DoCache cache,
std::shared_ptr<hb_font_t> fontIn,
TypefaceAscentDescent nonPortableMetricsIn,
const String& name,
const String& style)
: Typeface (name, style),
hbFont (std::move (fontIn)),
doCache (cache),
nonPortableMetrics (nonPortableMetricsIn)
{
if (doCache == DoCache::yes)
if (auto* c = MemoryFontCache::getInstance())
c->add ({ name, style }, { hbFont, nonPortableMetrics });
}
static std::tuple<MemoryBlock, TypefaceAscentDescent> getBlobForFont (const Font& font)
{
auto memory = loadFontAsset (font.getTypefaceName());
if (! memory.isEmpty())
return std::tuple (memory, findNonPortableMetricsForAsset (font.getTypefaceName()));
const auto file = findFontFile (font);
if (! file.exists())
{
// Failed to find file corresponding to this font
jassertfalse;
return {};
}
FileInputStream stream { file };
MemoryBlock result;
stream.readIntoMemoryBlock (result);
return std::tuple (stream.isExhausted() ? result : MemoryBlock{}, findNonPortableMetricsForFile (file));
}
static File findFontFile (const Font& font)
{
const String styles[] { font.getTypefaceStyle(),
FontStyleHelpers::getStyleName (font.isBold(), font.isItalic()),
{} };
for (const auto& style : styles)
if (auto file = getFontFile (font.getTypefaceName(), style); file.exists())
return file;
for (auto& file : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
if (file.getFileName().startsWith (font.getTypefaceName()))
return file;
return {};
}
static File getFontFile (const String& family, const String& fontStyle)
{
return "/system/fonts/" + family + (fontStyle.isNotEmpty() ? ("-" + fontStyle) : String{}) + ".ttf";
}
static MemoryBlock loadFontAsset (const String& typefaceName)
{
auto* env = getEnv();
const LocalRef assetManager { env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets) };
if (assetManager == nullptr)
return {};
const LocalRef inputStream { env->CallObjectMethod (assetManager,
AndroidAssetManager.open,
javaString ("fonts/" + typefaceName).get()) };
// Opening an input stream for an asset might throw if the asset isn't found
jniCheckHasExceptionOccurredAndClear();
if (inputStream == nullptr)
return {};
auto streamWrapper = makeAndroidInputStreamWrapper (inputStream);
if (streamWrapper == nullptr)
return {};
MemoryBlock result;
streamWrapper->readIntoMemoryBlock (result);
return streamWrapper->isExhausted() ? result : MemoryBlock{};
}
static File getCacheFileForData (Span<const std::byte> data)
{
static CriticalSection cs;
static std::map<String, File> cache;
JNIEnv* const env = getEnv();
const auto key = [&]
{
LocalRef digest (env->CallStaticObjectMethod (JavaMessageDigest, JavaMessageDigest.getInstance, javaString ("MD5").get()));
LocalRef bytes (env->NewByteArray ((int) data.size()));
jboolean ignore;
auto* jbytes = env->GetByteArrayElements (bytes.get(), &ignore);
memcpy (jbytes, data.data(), data.size());
env->ReleaseByteArrayElements (bytes.get(), jbytes, 0);
env->CallVoidMethod (digest.get(), JavaMessageDigest.update, bytes.get());
LocalRef result ((jbyteArray) env->CallObjectMethod (digest.get(), JavaMessageDigest.digest));
auto* md5Bytes = env->GetByteArrayElements (result.get(), &ignore);
const ScopeGuard scope { [&] { env->ReleaseByteArrayElements (result.get(), md5Bytes, 0); } };
return String::toHexString (md5Bytes, env->GetArrayLength (result.get()), 0);
}();
const ScopedLock lock (cs);
auto& mapEntry = cache[key];
if (mapEntry == File())
{
static const File cacheDirectory = []
{
auto appContext = getAppContext();
if (appContext == nullptr)
return File{};
auto* localEnv = getEnv();
LocalRef cacheFile (localEnv->CallObjectMethod (appContext.get(), AndroidContext.getCacheDir));
LocalRef jPath ((jstring) localEnv->CallObjectMethod (cacheFile.get(), JavaFile.getAbsolutePath));
return File (juceString (localEnv, jPath.get()));
}();
mapEntry = cacheDirectory.getChildFile ("bindata_" + key);
mapEntry.replaceWithData (data.data(), data.size());
}
return mapEntry;
}
static TypefaceAscentDescent findNonPortableMetricsForFile (File file)
{
auto* env = getEnv();
const LocalRef typeface { env->CallStaticObjectMethod (TypefaceClass,
TypefaceClass.createFromFile,
javaString (file.getFullPathName()).get()) };
return findNonPortableMetricsForTypeface (typeface);
}
static TypefaceAscentDescent findNonPortableMetricsForData (Span<const std::byte> bytes)
{
const auto file = getCacheFileForData (bytes);
return findNonPortableMetricsForFile (file);
}
static TypefaceAscentDescent findNonPortableMetricsForAsset (const String& name)
{
auto* env = getEnv();
const LocalRef assetManager { env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets) };
const LocalRef typeface { env->CallStaticObjectMethod (TypefaceClass,
TypefaceClass.createFromAsset,
assetManager.get(),
javaString ("fonts/" + name).get()) };
return findNonPortableMetricsForTypeface (typeface);
}
static TypefaceAscentDescent findNonPortableMetricsForTypeface (const LocalRef<jobject>& typeface)
{
constexpr auto referenceFontSize = 256.0f;
auto* env = getEnv();
jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/
| 2 /*FILTER_BITMAP_FLAG*/
| 4 /*DITHER_FLAG*/
| 128 /*SUBPIXEL_TEXT_FLAG*/;
const LocalRef paint { env->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags) };
env->CallObjectMethod (paint, AndroidPaint.setTypeface, typeface.get());
env->CallVoidMethod (paint, AndroidPaint.setTextSize, referenceFontSize);
const auto fullAscent = std::abs (env->CallFloatMethod (paint, AndroidPaint.ascent));
const auto fullDescent = std::abs (env->CallFloatMethod (paint, AndroidPaint.descent));
return TypefaceAscentDescent { fullAscent / referenceFontSize,
fullDescent / referenceFontSize };
}
std::shared_ptr<hb_font_t> hbFont;
DoCache doCache;
TypefaceAscentDescent nonPortableMetrics;
};
//==============================================================================
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return AndroidTypeface::from (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (Span<const std::byte> data)
{
return AndroidTypeface::from (data);
}
Typeface::Ptr Typeface::findSystemTypeface()
{
return AndroidTypeface::findSystemTypeface();
}
void Typeface::scanFolderForFonts (const File&)
{
jassertfalse; // not currently available
}
} // namespace juce
@@ -0,0 +1,583 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_USE_FONTCONFIG
using FcConfigPtr = std::unique_ptr<FcConfig, FunctionPointerDestructor<FcConfigDestroy>>;
using FcPatternPtr = std::unique_ptr<FcPattern, FunctionPointerDestructor<FcPatternDestroy>>;
using FcCharSetPtr = std::unique_ptr<FcCharSet, FunctionPointerDestructor<FcCharSetDestroy>>;
using FcLangSetPtr = std::unique_ptr<FcLangSet, FunctionPointerDestructor<FcLangSetDestroy>>;
#endif
struct FTLibWrapper final : public ReferenceCountedObject
{
FTLibWrapper()
{
if (FT_Init_FreeType (&library) != 0)
{
library = {};
DBG ("Failed to initialize FreeType");
}
}
~FTLibWrapper()
{
if (library != nullptr)
FT_Done_FreeType (library);
}
#if JUCE_USE_FONTCONFIG
const FcConfigPtr fcConfig { FcInitLoadConfigAndFonts() };
#endif
FT_Library library = {};
using Ptr = ReferenceCountedObjectPtr<FTLibWrapper>;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTLibWrapper)
};
//==============================================================================
struct FTFaceWrapper final : public ReferenceCountedObject
{
using Ptr = ReferenceCountedObjectPtr<FTFaceWrapper>;
static FTFaceWrapper::Ptr selectUnicodeCharmap (FTFaceWrapper::Ptr face)
{
if (face != nullptr)
if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0)
FT_Set_Charmap (face->face, face->face->charmaps[0]);
return face;
}
static FTFaceWrapper::Ptr from (const FTLibWrapper::Ptr& ftLib, const File& file, int faceIndex)
{
FT_Face result{};
if (FT_New_Face (ftLib->library, file.getFullPathName().toUTF8(), faceIndex, &result) != 0)
return {};
return selectUnicodeCharmap (new FTFaceWrapper (ftLib, result));
}
static FTFaceWrapper::Ptr from (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex)
{
MemoryBlock storage (data, dataSize);
FT_Face result{};
if (FT_New_Memory_Face (ftLib->library,
static_cast<const FT_Byte*> (storage.getData()),
(FT_Long) storage.getSize(),
faceIndex,
&result) != 0)
return {};
return selectUnicodeCharmap (new FTFaceWrapper (ftLib, result, std::move (storage)));
}
FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, FT_Face faceIn, MemoryBlock mb = {})
: library (ftLib), savedFaceData (std::move (mb)), face (faceIn) {}
~FTFaceWrapper()
{
if (face != nullptr)
FT_Done_Face (face);
}
FTLibWrapper::Ptr library;
MemoryBlock savedFaceData;
FT_Face face = {};
JUCE_LEAK_DETECTOR (FTFaceWrapper)
};
//==============================================================================
class FTTypefaceList final : private DeletedAtShutdown
{
public:
FTTypefaceList()
{
scanFontPaths (getDefaultFontDirectories());
}
~FTTypefaceList() override
{
clearSingletonInstance();
}
//==============================================================================
struct KnownTypeface
{
explicit KnownTypeface (const FTFaceWrapper& face)
: family (face.face->family_name),
style (face.face->style_name),
faceIndex ((int) face.face->face_index),
flags (((face.face->style_flags & FT_STYLE_FLAG_BOLD) ? bold : 0)
| ((face.face->style_flags & FT_STYLE_FLAG_ITALIC) ? italic : 0)
| ((face.face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) ? monospaced : 0)
| (isFaceSansSerif (family) ? sansSerif : 0))
{
}
virtual ~KnownTypeface() = default;
virtual FTFaceWrapper::Ptr create (FTLibWrapper::Ptr) const = 0;
virtual bool holdsFace (FTFaceWrapper::Ptr) const { return false; }
enum Flag
{
bold = 1 << 0,
italic = 1 << 1,
monospaced = 1 << 2,
sansSerif = 1 << 3,
};
const String family, style;
const int faceIndex;
const int flags;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownTypeface)
};
struct FileTypeface : public KnownTypeface
{
FileTypeface (const FTFaceWrapper& face, const File& fileIn)
: KnownTypeface (face), file (fileIn) {}
FTFaceWrapper::Ptr create (FTLibWrapper::Ptr lib) const override
{
return FTFaceWrapper::from (lib, file, faceIndex);
}
const File file;
};
struct CachedTypeface : public KnownTypeface
{
explicit CachedTypeface (FTFaceWrapper::Ptr ptr)
: KnownTypeface (*ptr), face (ptr) {}
FTFaceWrapper::Ptr create (FTLibWrapper::Ptr) const override
{
return face;
}
bool holdsFace (FTFaceWrapper::Ptr p) const override
{
return face == p;
}
FTFaceWrapper::Ptr face;
};
//==============================================================================
FTFaceWrapper::Ptr createFace (const void* data, size_t dataSize, int index)
{
return FTFaceWrapper::from (library, data, dataSize, index);
}
FTFaceWrapper::Ptr createFace (const File& file, int index)
{
return FTFaceWrapper::from (library, file, index);
}
FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle)
{
auto ftFace = matchTypeface (fontName, fontStyle);
if (ftFace == nullptr) ftFace = matchTypeface (fontName, "Regular");
if (ftFace == nullptr) ftFace = matchTypeface (fontName, {});
if (ftFace != nullptr)
return ftFace->create (library);
return nullptr;
}
//==============================================================================
StringArray findAllFamilyNames() const
{
std::set<String> set;
for (const auto& face : faces)
set.insert (face->family);
StringArray s;
for (const auto& family : set)
s.add (family);
return s;
}
StringArray findAllTypefaceStyles (const String& family) const
{
StringArray s;
for (const auto& face : faces)
if (face->family == family)
s.addIfNotAlreadyThere (face->style);
// scanFontPaths ensures that regular styles are ordered before other styles
return s;
}
void scanFontPaths (const StringArray& paths)
{
for (auto& path : paths)
{
for (const auto& iter : RangedDirectoryIterator (File::getCurrentWorkingDirectory().getChildFile (path), true))
{
if (iter.getFile().hasFileExtension ("ttf;pfb;pcf;otf"))
scanFont (iter.getFile());
}
}
std::sort (faces.begin(), faces.end(), [] (const auto& a, const auto& b)
{
const auto tie = [] (const KnownTypeface& t)
{
return std::make_tuple (t.family,
t.flags,
t.style,
t.faceIndex);
};
return tie (*a) < tie (*b);
});
}
void getMonospacedNames (StringArray& monoSpaced) const
{
for (const auto& face : faces)
if (face->flags & KnownTypeface::monospaced)
monoSpaced.addIfNotAlreadyThere (face->family);
}
void getSerifNames (StringArray& serif) const
{
for (const auto& face : faces)
if ((face->flags & (KnownTypeface::sansSerif | KnownTypeface::monospaced)) == 0)
serif.addIfNotAlreadyThere (face->family);
}
void getSansSerifNames (StringArray& sansSerif) const
{
for (const auto& face : faces)
if (face->flags & KnownTypeface::sansSerif)
sansSerif.addIfNotAlreadyThere (face->family);
}
void addMemoryFace (FTFaceWrapper::Ptr ptr)
{
faces.insert (faces.begin(), std::make_unique<CachedTypeface> (ptr));
}
void removeMemoryFace (FTFaceWrapper::Ptr ptr)
{
const auto iter = std::find_if (faces.begin(), faces.end(), [&] (const auto& face)
{
return face->holdsFace (ptr);
});
if (iter != faces.end())
faces.erase (iter);
}
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL_INLINE (FTTypefaceList)
FTLibWrapper::Ptr getLibrary() const { return library; }
private:
FTLibWrapper::Ptr library = new FTLibWrapper;
std::vector<std::unique_ptr<KnownTypeface>> faces;
static StringArray getDefaultFontDirectories();
void scanFont (const File& file)
{
int faceIndex = 0;
int numFaces = 0;
do
{
if (auto face = FTFaceWrapper::from (library, file, faceIndex))
{
if (face->face != nullptr)
{
if (faceIndex == 0)
numFaces = (int) face->face->num_faces;
faces.push_back (std::make_unique<FileTypeface> (*face, file));
}
}
++faceIndex;
}
while (faceIndex < numFaces);
}
const KnownTypeface* matchTypeface (const String& familyName, const String& style) const noexcept
{
for (const auto& face : faces)
if (face->family == familyName
&& (face->style.equalsIgnoreCase (style) || style.isEmpty()))
return face.get();
return nullptr;
}
static bool isFaceSansSerif (const String& family)
{
static const char* sansNames[] = { "Sans", "Verdana", "Arial", "Ubuntu" };
for (auto* name : sansNames)
if (family.containsIgnoreCase (name))
return true;
return false;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTTypefaceList)
};
//==============================================================================
class FreeTypeTypeface final : public Typeface
{
using Ptr = ReferenceCountedObjectPtr<FreeTypeTypeface>;
enum class DoCache
{
no,
yes,
};
public:
static Typeface::Ptr from (const Font& font)
{
const auto name = font.getTypefaceName();
const auto style = font.getTypefaceStyle();
auto face = FTTypefaceList::getInstance()->createFace (name, style);
if (face == nullptr)
return {};
auto* hbFace = hb_ft_face_create_referenced (face->face);
const ScopeGuard scope { [&] { hb_face_destroy (hbFace); } };
HbFont hb { hb_font_create (hbFace) };
if (hb == nullptr)
return {};
FontStyleHelpers::initSynthetics (hb.get(), font);
return new FreeTypeTypeface (DoCache::no, face, std::move (hb), name, style);
}
static Typeface::Ptr from (Span<const std::byte> data, int index = 0)
{
auto face = FTTypefaceList::getInstance()->createFace (data.data(), data.size(), index);
if (face == nullptr)
return {};
auto* hbFace = hb_ft_face_create_referenced (face->face);
const ScopeGuard scope { [&] { hb_face_destroy (hbFace); } };
HbFont hb { hb_font_create (hbFace) };
if (hb == nullptr)
return {};
return new FreeTypeTypeface (DoCache::yes, face, std::move (hb), face->face->family_name, face->face->style_name);
}
Native getNativeDetails() const override
{
return Native { hb.get(), nonPortableMetrics };
}
Typeface::Ptr createSystemFallback ([[maybe_unused]] const String& text,
[[maybe_unused]] const String& language) const override
{
#if JUCE_USE_FONTCONFIG
auto* cache = TypefaceFileCache::getInstance();
if (cache == nullptr)
return {};
FcPatternPtr pattern { FcPatternCreate() };
{
FcValue value{};
value.type = FcTypeString;
value.u.s = unalignedPointerCast<const FcChar8*> (ftFace->face->family_name);
FcPatternAddWeak (pattern.get(), FC_FAMILY, value, FcFalse);
}
{
FcValue value{};
value.type = FcTypeString;
value.u.s = unalignedPointerCast<const FcChar8*> (ftFace->face->style_name);
FcPatternAddWeak (pattern.get(), FC_STYLE, value, FcFalse);
}
{
const FcCharSetPtr charset { FcCharSetCreate() };
for (const auto& character : text)
FcCharSetAddChar (charset.get(), (FcChar32) character);
FcPatternAddCharSet (pattern.get(), FC_CHARSET, charset.get());
}
if (language.isNotEmpty())
{
const FcLangSetPtr langset { FcLangSetCreate() };
FcLangSetAdd (langset.get(), unalignedPointerCast<const FcChar8*> (language.toRawUTF8()));
FcPatternAddLangSet (pattern.get(), FC_LANG, langset.get());
}
return fromPattern (pattern.get());
#else
// Font substitution will not work unless fontconfig is enabled.
jassertfalse;
return nullptr;
#endif
}
~FreeTypeTypeface() override
{
if (doCache == DoCache::yes)
if (auto* list = FTTypefaceList::getInstanceWithoutCreating())
list->removeMemoryFace (ftFace);
}
static Typeface::Ptr findSystemTypeface()
{
#if JUCE_USE_FONTCONFIG
FcPatternPtr pattern { FcNameParse (unalignedPointerCast<const FcChar8*> ("system-ui")) };
return fromPattern (pattern.get());
#else
return nullptr;
#endif
}
private:
#if JUCE_USE_FONTCONFIG
static Typeface::Ptr fromPattern (FcPattern* pattern)
{
auto* cache = TypefaceFileCache::getInstance();
if (cache == nullptr)
return {};
const auto library = FTTypefaceList::getInstance()->getLibrary();
FcConfigSubstitute (library->fcConfig.get(), pattern, FcMatchPattern);
FcDefaultSubstitute (pattern);
FcResult result{};
const FcPatternPtr matched { FcFontMatch (library->fcConfig.get(), pattern, &result) };
if (result != FcResultMatch)
return {};
FcChar8* fileString{};
if (FcPatternGetString (matched.get(), FC_FILE, 0, &fileString) != FcResultMatch)
return {};
int index{};
if (FcPatternGetInteger (matched.get(), FC_INDEX, 0, &index) != FcResultMatch)
return {};
const File file { String { CharPointer_UTF8 { unalignedPointerCast<const char*> (fileString) } } };
return cache->get ({ file, index }, [] (const TypefaceFileAndIndex& f) -> Typeface::Ptr
{
auto face = FTTypefaceList::getInstance()->createFace (f.file, f.index);
if (face == nullptr)
return {};
const HbFace hbFace { hb_ft_face_create_referenced (face->face) };
HbFont cachedFont { hb_font_create (hbFace.get()) };
if (cachedFont == nullptr)
return {};
return new FreeTypeTypeface (DoCache::no, face, std::move (cachedFont), face->face->family_name, face->face->style_name);
});
}
#endif
FreeTypeTypeface (DoCache cache,
FTFaceWrapper::Ptr ftFaceIn,
HbFont hbIn,
const String& nameIn,
const String& styleIn)
: Typeface (nameIn, styleIn),
ftFace (ftFaceIn),
hb (std::move (hbIn)),
doCache (cache)
{
if (doCache == DoCache::yes)
if (auto* list = FTTypefaceList::getInstance())
list->addMemoryFace (ftFace);
}
FTFaceWrapper::Ptr ftFace;
HbFont hb;
DoCache doCache;
TypefaceAscentDescent nonPortableMetrics { (float) std::abs (ftFace->face->ascender) / (float) ftFace->face->units_per_EM,
(float) std::abs (ftFace->face->descender) / (float) ftFace->face->units_per_EM };
JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface)
};
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return FreeTypeTypeface::from (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (Span<const std::byte> data)
{
return FreeTypeTypeface::from (data);
}
Typeface::Ptr Typeface::findSystemTypeface()
{
return FreeTypeTypeface::findSystemTypeface();
}
} // namespace juce
@@ -0,0 +1,216 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
static std::unique_ptr<XmlElement> findFontsConfFile()
{
static const char* pathsToSearch[] = { "/etc/fonts/fonts.conf",
"/usr/share/fonts/fonts.conf",
"/usr/local/etc/fonts/fonts.conf",
"/usr/share/defaults/fonts/fonts.conf" };
for (auto* path : pathsToSearch)
if (auto xml = parseXML (File (path)))
return xml;
return {};
}
StringArray FTTypefaceList::getDefaultFontDirectories()
{
StringArray fontDirs;
fontDirs.addTokens (String (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH"))), ";,", "");
fontDirs.removeEmptyStrings (true);
if (fontDirs.isEmpty())
{
if (auto fontsInfo = findFontsConfFile())
{
for (auto* e : fontsInfo->getChildWithTagNameIterator ("dir"))
{
auto fontPath = e->getAllSubText().trim();
if (fontPath.isNotEmpty())
{
if (e->getStringAttribute ("prefix") == "xdg")
{
auto xdgDataHome = SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", {});
if (xdgDataHome.trimStart().isEmpty())
xdgDataHome = "~/.local/share";
fontPath = File (xdgDataHome).getChildFile (fontPath).getFullPathName();
}
fontDirs.add (fontPath);
}
}
}
}
if (fontDirs.isEmpty())
fontDirs.add ("/usr/X11R6/lib/X11/fonts");
fontDirs.removeDuplicates (false);
return fontDirs;
}
void Typeface::scanFolderForFonts (const File& folder)
{
FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
}
StringArray Font::findAllTypefaceNames()
{
return FTTypefaceList::getInstance()->findAllFamilyNames();
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
}
//==============================================================================
struct DefaultFontInfo
{
DefaultFontInfo()
: defaultSans (getDefaultSansSerifFontName()),
defaultSerif (getDefaultSerifFontName()),
defaultFixed (getDefaultMonospacedFontName())
{
}
String getRealFontName (const String& faceName) const
{
if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans;
if (faceName == Font::getDefaultSerifFontName()) return defaultSerif;
if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed;
return faceName;
}
String defaultSans, defaultSerif, defaultFixed;
private:
template <typename Range>
static String pickBestFont (const StringArray& names, Range&& choicesArray)
{
for (auto& choice : choicesArray)
if (names.contains (choice, true))
return choice;
for (auto& choice : choicesArray)
for (auto& name : names)
if (name.startsWithIgnoreCase (choice))
return name;
for (auto& choice : choicesArray)
for (auto& name : names)
if (name.containsIgnoreCase (choice))
return name;
for (auto& name : names)
if (name.isNotEmpty())
return name;
jassertfalse;
return {};
}
static String getDefaultSansSerifFontName()
{
StringArray allFonts;
FTTypefaceList::getInstance()->getSansSerifNames (allFonts);
static constexpr const char* targets[] { "Verdana",
"Bitstream Vera Sans",
"Luxi Sans",
"Liberation Sans",
"DejaVu Sans",
"Sans" };
return pickBestFont (allFonts, targets);
}
static String getDefaultSerifFontName()
{
StringArray allFonts;
FTTypefaceList::getInstance()->getSerifNames (allFonts);
static constexpr const char* targets[] { "Bitstream Vera Serif",
"Times",
"Nimbus Roman",
"Liberation Serif",
"DejaVu Serif",
"Serif" };
return pickBestFont (allFonts, targets);
}
static String getDefaultMonospacedFontName()
{
StringArray allFonts;
FTTypefaceList::getInstance()->getMonospacedNames (allFonts);
static constexpr const char* targets[] { "DejaVu Sans Mono",
"Bitstream Vera Sans Mono",
"Sans Mono",
"Liberation Mono",
"Courier",
"DejaVu Mono",
"Mono" };
return pickBestFont (allFonts, targets);
}
JUCE_DECLARE_NON_COPYABLE (DefaultFontInfo)
};
Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font)
{
static const DefaultFontInfo defaultInfo;
Font f (font);
const auto name = font.getTypefaceName();
const auto realName = defaultInfo.getRealFontName (name);
if (realName.isEmpty())
return nullptr;
f.setTypefaceName (realName);
return Typeface::createSystemTypefaceFor (f);
}
} // namespace juce
@@ -0,0 +1,480 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
// This symbol is available on all the platforms we support, but not declared in the CoreText headers on older platforms.
extern "C" CTFontRef CTFontCreateForStringWithLanguage (CTFontRef currentFont,
CFStringRef string,
CFRange range,
CFStringRef language);
class CoreTextTypeface final : public Typeface
{
static auto& getRegistered()
{
class Registered
{
public:
void add (CTFontRef ref)
{
const std::scoped_lock lock { mutex };
set.emplace (addOwner (ref));
}
void remove (CTFontRef ref)
{
const std::scoped_lock lock { mutex };
set.erase (addOwner (ref));
}
CFUniquePtr<CTFontRef> findMatch (const String& fontName, const String& fontStyle) const
{
const std::scoped_lock lock { mutex };
for (auto& item : set)
{
const auto keyAsString = [&] (auto key)
{
return String::fromCFString (CFUniquePtr<CFStringRef> { CTFontCopyName (item.get(), key) }.get());
};
const auto family = keyAsString (kCTFontFamilyNameKey);
const auto style = keyAsString (kCTFontStyleNameKey);
if (fontName == family && (style.isEmpty() || fontStyle.equalsIgnoreCase (style)))
return addOwner (item.get());
}
return nullptr;
}
std::vector<CFUniquePtr<CTFontRef>> findAllStylesForFamily (const String& fontName) const
{
const std::scoped_lock lock { mutex };
std::vector<CFUniquePtr<CTFontRef>> result;
for (auto& item : set)
{
const auto keyAsString = [&] (auto key)
{
return String::fromCFString (CFUniquePtr<CFStringRef> { CTFontCopyName (item.get(), key) }.get());
};
const auto family = keyAsString (kCTFontFamilyNameKey);
if (fontName == family)
result.emplace_back (addOwner (item.get()));
}
return result;
}
std::set<String> getRegisteredFamilies() const
{
const std::scoped_lock lock { mutex };
std::set<String> result;
for (const auto& item : set)
{
const CFUniquePtr<CFStringRef> family { CTFontCopyName (item.get(), kCTFontFamilyNameKey) };
result.insert (String::fromCFString (family.get()));
}
return result;
}
private:
static CFUniquePtr<CTFontRef> addOwner (CTFontRef reference)
{
CFRetain (reference);
return CFUniquePtr<CTFontRef> { reference };
}
std::set<CFUniquePtr<CTFontRef>> set;
mutable std::mutex mutex;
};
static Registered registered;
return registered;
}
public:
static Typeface::Ptr from (const Font& font)
{
auto ctFont = [&]() -> CFUniquePtr<CTFontRef>
{
if (auto f = getRegistered().findMatch (font.getTypefaceName(), font.getTypefaceStyle()))
return f;
CFUniquePtr<CFStringRef> cfFontFamily (FontStyleHelpers::getConcreteFamilyName (font).toCFString());
if (cfFontFamily == nullptr)
return {};
CFUniquePtr<CFStringRef> cfFontStyle (findBestAvailableStyle (font).toCFString());
if (cfFontStyle == nullptr)
return {};
CFStringRef keys[] { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
CFTypeRef values[] { cfFontFamily.get(), cfFontStyle.get() };
CFUniquePtr<CFDictionaryRef> fontDescAttributes (CFDictionaryCreate (nullptr,
(const void**) &keys,
(const void**) &values,
numElementsInArray (keys),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
if (fontDescAttributes == nullptr)
return {};
CFUniquePtr<CTFontDescriptorRef> ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get()));
if (ctFontDescRef == nullptr)
return {};
return CFUniquePtr<CTFontRef> { CTFontCreateWithFontDescriptor (ctFontDescRef.get(), 1, nullptr) };
}();
if (ctFont == nullptr)
return {};
HbFont result { hb_coretext_font_create (ctFont.get()) };
if (result == nullptr)
return {};
FontStyleHelpers::initSynthetics (result.get(), font);
return new CoreTextTypeface (std::move (ctFont), std::move (result), font.getTypefaceName(), font.getTypefaceStyle());
}
static Typeface::Ptr from (Span<const std::byte> data)
{
// We can't use CFDataCreate here as this triggers a false positive in ASAN
// so copy the data manually and use CFDataCreateWithBytesNoCopy
MemoryBlock copy { data.data(), data.size() };
const CFUniquePtr<CFDataRef> cfData { CFDataCreateWithBytesNoCopy (kCFAllocatorDefault,
static_cast<const UInt8*> (copy.getData()),
(CFIndex) copy.getSize(),
kCFAllocatorNull) };
if (cfData == nullptr)
return {};
#if JUCE_IOS
// Workaround for a an obscure iOS bug which can cause the app to dead-lock
// when loading custom type faces. See: http://www.openradar.me/18778790 and
// http://stackoverflow.com/questions/40242370/app-hangs-in-simulator
[UIFont systemFontOfSize: 12];
#endif
const CFUniquePtr<CGDataProviderRef> provider { CGDataProviderCreateWithCFData (cfData.get()) };
if (provider == nullptr)
return {};
const CFUniquePtr<CGFontRef> font { CGFontCreateWithDataProvider (provider.get()) };
if (font == nullptr)
return {};
CFUniquePtr<CTFontRef> ctFont { CTFontCreateWithGraphicsFont (font.get(), 1.0f, {}, {}) };
if (ctFont == nullptr)
return {};
HbFont result { hb_coretext_font_create (ctFont.get()) };
if (result == nullptr)
return {};
const CFUniquePtr<CFStringRef> family { CTFontCopyName (ctFont.get(), kCTFontFamilyNameKey) };
const CFUniquePtr<CFStringRef> style { CTFontCopyName (ctFont.get(), kCTFontStyleNameKey) };
return new CoreTextTypeface (std::move (ctFont),
std::move (result),
String::fromCFString (family.get()),
String::fromCFString (style.get()),
std::move (copy));
}
Native getNativeDetails() const override
{
return Native { hb.get(), nonPortableMetrics };
}
Typeface::Ptr createSystemFallback (const String& c, const String& language) const override
{
const CFUniquePtr<CFStringRef> cfText { c.toCFString() };
const CFUniquePtr<CFStringRef> cfLanguage { language.toCFString() };
auto* old = ctFont.get();
const CFUniquePtr<CFStringRef> oldName { CTFontCopyFamilyName (old) };
const CFUniquePtr<CTFontDescriptorRef> oldDescriptor { CTFontCopyFontDescriptor (old) };
const CFUniquePtr<CFStringRef> oldStyle { (CFStringRef) CTFontDescriptorCopyAttribute (oldDescriptor.get(),
kCTFontStyleNameAttribute) };
CFUniquePtr<CTFontRef> newFont { CTFontCreateForStringWithLanguage (old,
cfText.get(),
CFRangeMake (0, CFStringGetLength (cfText.get())),
cfLanguage.get()) };
const CFUniquePtr<CFStringRef> newName { CTFontCopyFamilyName (newFont.get()) };
const CFUniquePtr<CTFontDescriptorRef> descriptor { CTFontCopyFontDescriptor (newFont.get()) };
const CFUniquePtr<CFStringRef> newStyle { (CFStringRef) CTFontDescriptorCopyAttribute (descriptor.get(),
kCTFontStyleNameAttribute) };
HbFont result { hb_coretext_font_create (newFont.get()) };
if (result == nullptr)
return {};
return new CoreTextTypeface { std::move (newFont),
std::move (result),
String::fromCFString (newName.get()),
String::fromCFString (newStyle.get()),
{} };
}
static std::set<String> getRegisteredFamilies()
{
return getRegistered().getRegisteredFamilies();
}
static std::vector<CFUniquePtr<CTFontRef>> findRegisteredStylesForFamily (const String& family)
{
return getRegistered().findAllStylesForFamily (family);
}
~CoreTextTypeface() override
{
getRegistered().remove (ctFont.get());
}
CTFontRef getFontRef() const
{
return ctFont.get();
}
static Typeface::Ptr findSystemTypeface()
{
CFUniquePtr<CTFontRef> defaultCtFont (CTFontCreateUIFontForLanguage (kCTFontUIFontSystem, 0.0, nullptr));
const CFUniquePtr<CFStringRef> newName { CTFontCopyFamilyName (defaultCtFont.get()) };
const CFUniquePtr<CTFontDescriptorRef> descriptor { CTFontCopyFontDescriptor (defaultCtFont.get()) };
const CFUniquePtr<CFStringRef> newStyle { (CFStringRef) CTFontDescriptorCopyAttribute (descriptor.get(),
kCTFontStyleNameAttribute) };
HbFont result { hb_coretext_font_create (defaultCtFont.get()) };
if (result == nullptr)
return {};
return new CoreTextTypeface { std::move (defaultCtFont),
std::move (result),
String::fromCFString (newName.get()),
String::fromCFString (newStyle.get()),
{} };
}
private:
CoreTextTypeface (CFUniquePtr<CTFontRef> nativeFont,
HbFont fontIn,
const String& name,
const String& style,
MemoryBlock data = {})
: Typeface (name, style),
ctFont (std::move (nativeFont)),
hb (std::move (fontIn)),
storage (std::move (data))
{
if (! storage.isEmpty())
getRegistered().add (ctFont.get());
}
static String findBestAvailableStyle (const Font& font)
{
const auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName());
const auto style = font.getTypefaceStyle();
if (availableStyles.contains (style))
return style;
return availableStyles[0];
}
// We store this, rather than calling hb_coretext_font_get_ct_font, because harfbuzz may
// override the font cascade list in the returned font.
CFUniquePtr<CTFontRef> ctFont;
HbFont hb;
MemoryBlock storage;
TypefaceAscentDescent nonPortableMetrics = [&]
{
const CFUniquePtr<CGFontRef> cgFont { CTFontCopyGraphicsFont (ctFont.get(), nullptr) };
const auto upem = (float) CGFontGetUnitsPerEm (cgFont.get());
return TypefaceAscentDescent { (float) std::abs (CGFontGetAscent (cgFont.get()) / upem),
(float) std::abs (CGFontGetDescent (cgFont.get()) / upem) };
}();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreTextTypeface)
};
//==============================================================================
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return CoreTextTypeface::from (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (Span<const std::byte> data)
{
return CoreTextTypeface::from (data);
}
void Typeface::scanFolderForFonts (const File& folder)
{
for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf"))
if (auto urlref = CFUniquePtr<CFURLRef> (CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true)))
CTFontManagerRegisterFontsForURL (urlref.get(), kCTFontManagerScopeProcess, nullptr);
}
Typeface::Ptr Typeface::findSystemTypeface()
{
return CoreTextTypeface::findSystemTypeface();
}
StringArray Font::findAllTypefaceNames()
{
StringArray names;
auto nameSet = CoreTextTypeface::getRegisteredFamilies();
CFUniquePtr<CTFontCollectionRef> fontCollectionRef (CTFontCollectionCreateFromAvailableFonts (nullptr));
CFUniquePtr<CFArrayRef> fontDescriptorArray (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get()));
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i)
{
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i);
CFUniquePtr<CFStringRef> cfsFontFamily ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute));
nameSet.insert (String::fromCFString (cfsFontFamily.get()));
}
for (auto& item : nameSet)
names.add (item);
return names;
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
if (FontStyleHelpers::isPlaceholderFamilyName (family))
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
std::set<String> uniqueResults;
StringArray orderedResults;
for (const auto& font : CoreTextTypeface::findRegisteredStylesForFamily (family))
{
const CFUniquePtr<CTFontDescriptorRef> descriptor (CTFontCopyFontDescriptor (font.get()));
const CFUniquePtr<CFStringRef> cfsFontStyle ((CFStringRef) CTFontDescriptorCopyAttribute (descriptor.get(), kCTFontStyleNameAttribute));
const auto name = String::fromCFString (cfsFontStyle.get());
if (uniqueResults.insert (name).second)
orderedResults.add (name);
}
CFUniquePtr<CFStringRef> cfsFontFamily (family.toCFString());
CFStringRef keys[] { kCTFontFamilyNameAttribute };
CFTypeRef values[] { cfsFontFamily.get() };
CFUniquePtr<CFDictionaryRef> fontDescAttributes (CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CFUniquePtr<CTFontDescriptorRef> ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get()));
CFUniquePtr<CFArrayRef> fontFamilyArray (CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks));
CFUniquePtr<CTFontCollectionRef> fontCollectionRef (CTFontCollectionCreateWithFontDescriptors (fontFamilyArray.get(), nullptr));
if (auto fontDescriptorArray = CFUniquePtr<CFArrayRef> (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get())))
{
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i)
{
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i);
CFUniquePtr<CFStringRef> cfsFontStyle ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute));
const auto name = String::fromCFString (cfsFontStyle.get());
if (uniqueResults.insert (name).second)
orderedResults.add (name);
}
}
return orderedResults;
}
struct DefaultFontNames
{
#if JUCE_IOS
String defaultSans { "Helvetica" },
defaultSerif { "Times New Roman" },
defaultFixed { "Courier New" };
#else
String defaultSans { "Lucida Grande" },
defaultSerif { "Times New Roman" },
defaultFixed { "Menlo" };
#endif
};
Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font)
{
static DefaultFontNames defaultNames;
auto newFont = font;
auto faceName = font.getTypefaceName();
if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
if (font.getTypefaceStyle() == getDefaultStyle())
newFont.setTypefaceStyle ("Regular");
return Typeface::createSystemTypefaceFor (newFont);
}
} // namespace juce
@@ -0,0 +1,43 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
{
return SoftwareImageType().create (format, width, height, clearImage);
}
} // namespace juce
@@ -0,0 +1,39 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, int);
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
}
@@ -0,0 +1,39 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, int);
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
}
@@ -0,0 +1,150 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
extern Image JUCE_API getIconFromApplication (const String&, int);
static Image getIconFromIcnsFile (const File& icnsFile, const int size)
{
FileInputStream stream (icnsFile);
if (! stream.openedOk())
return {};
const int numHeaderSectionBytes = 4;
char headerSection [numHeaderSectionBytes];
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes
|| headerSection[0] != 'i'
|| headerSection[1] != 'c'
|| headerSection[2] != 'n'
|| headerSection[3] != 's')
return {};
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes)
return {};
const auto dataSize = juce::ByteOrder::bigEndianInt (headerSection);
if (dataSize <= 0)
return {};
OwnedArray<juce::ImageFileFormat> internalFormats;
internalFormats.add (new PNGImageFormat());
internalFormats.add (new JPEGImageFormat());
Array<Image> images;
auto maxWidth = 0;
auto maxWidthIndex = -1;
while (stream.getPosition() < dataSize)
{
const auto sectionStart = stream.getPosition();
if (! stream.setPosition (sectionStart + 4))
break;
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes)
break;
const auto sectionSize = ByteOrder::bigEndianInt (headerSection);
if (sectionSize <= 0)
break;
const auto sectionDataStart = stream.getPosition();
for (auto* fmt : internalFormats)
{
if (fmt->canUnderstand (stream))
{
stream.setPosition (sectionDataStart);
images.add (fmt->decodeImage (stream));
const auto lastImageIndex = images.size() - 1;
const auto lastWidth = images.getReference (lastImageIndex).getWidth();
if (lastWidth > maxWidth)
{
maxWidthIndex = lastImageIndex;
maxWidth = lastWidth;
}
}
stream.setPosition (sectionDataStart);
}
stream.setPosition (sectionStart + sectionSize);
}
return maxWidthIndex == -1 ? juce::Image()
: images.getReference (maxWidthIndex).rescaled (size, size, Graphics::ResamplingQuality::highResamplingQuality);
}
Image JUCE_API getIconFromApplication (const String& applicationPath, const int size)
{
if (auto pathCFString = CFUniquePtr<CFStringRef> (CFStringCreateWithCString (kCFAllocatorDefault, applicationPath.toRawUTF8(), kCFStringEncodingUTF8)))
{
if (auto url = CFUniquePtr<CFURLRef> (CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pathCFString.get(), kCFURLPOSIXPathStyle, 1)))
{
if (auto appBundle = CFUniquePtr<CFBundleRef> (CFBundleCreate (kCFAllocatorDefault, url.get())))
{
if (CFTypeRef infoValue = CFBundleGetValueForInfoDictionaryKey (appBundle.get(), CFSTR ("CFBundleIconFile")))
{
if (CFGetTypeID (infoValue) == CFStringGetTypeID())
{
CFStringRef iconFilename = reinterpret_cast<CFStringRef> (infoValue);
CFStringRef resourceURLSuffix = CFStringHasSuffix (iconFilename, CFSTR (".icns")) ? nullptr : CFSTR ("icns");
if (auto iconURL = CFUniquePtr<CFURLRef> (CFBundleCopyResourceURL (appBundle.get(), iconFilename, resourceURLSuffix, nullptr)))
{
if (auto iconPath = CFUniquePtr<CFStringRef> (CFURLCopyFileSystemPath (iconURL.get(), kCFURLPOSIXPathStyle)))
{
File icnsFile (CFStringGetCStringPtr (iconPath.get(), CFStringGetSystemEncoding()));
return getIconFromIcnsFile (icnsFile, size);
}
}
}
}
}
}
}
return {};
}
} // namespace juce
@@ -0,0 +1,39 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, int);
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
}
File diff suppressed because it is too large Load Diff