First Commit
This commit is contained in:
@@ -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
|
||||
+1942
File diff suppressed because it is too large
Load Diff
+186
@@ -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
|
||||
+112
@@ -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
Reference in New Issue
Block a user