Add initial directwrite to svg conversion.

This commit is contained in:
jmsgrogan 2023-01-10 17:24:37 +00:00
parent 2c825adc1d
commit b7f75f903e
15 changed files with 571 additions and 7 deletions

View file

@ -28,3 +28,4 @@ target_include_directories(${PLUGIN_NAME} PUBLIC
) )
target_link_libraries(${PLUGIN_NAME} PUBLIC core visual_elements ntk_math) target_link_libraries(${PLUGIN_NAME} PUBLIC core visual_elements ntk_math)
set_property(TARGET ${PLUGIN_NAME} PROPERTY FOLDER plugins) set_property(TARGET ${PLUGIN_NAME} PROPERTY FOLDER plugins)
set_target_properties( ${PLUGIN_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON )

View file

@ -13,7 +13,7 @@
TEST_CASE(TestBlochSphereNode, "quantum_computing") TEST_CASE(TestBlochSphereNode, "quantum_computing")
{ {
auto node = std::make_unique<BlochSphereNode>(); auto node = std::make_unique<BlochSphereNode>(DiscretePoint(0.5, 0.5));
Qubit state({ 1.0, 0.0 }, { 0.0, 0.0 }); Qubit state({ 1.0, 0.0 }, { 0.0, 0.0 });

View file

@ -8,6 +8,12 @@ list(APPEND fonts_LIB_INCLUDES
TrueTypeFont.cpp TrueTypeFont.cpp
FontsManager.cpp FontsManager.cpp
FontGlyph.cpp FontGlyph.cpp
FontReader.h
TrueTypeFont.h
FontsManager.h
FontGlyph.h
IFont.h
IFontEngine.h
) )
if(UNIX) if(UNIX)
@ -24,15 +30,26 @@ if(UNIX)
else() else()
message(STATUS "Freetype not found - skipping support") message(STATUS "Freetype not found - skipping support")
endif() endif()
else()
list(APPEND fonts_LIB_INCLUDES
directx/DirectWriteFontEngine.h
directx/DirectWriteHelpers.h
directx/DirectWriteFontEngine.cpp
directx/DirectWriteHelpers.cpp
)
list(APPEND fonts_LIB_DEPENDS
Dwrite.lib D2d1.lib uuid.lib
)
endif() endif()
add_library(${MODULE_NAME} SHARED ${fonts_LIB_INCLUDES}) add_library(${MODULE_NAME} SHARED ${fonts_LIB_INCLUDES})
target_include_directories(${MODULE_NAME} PUBLIC target_include_directories(${MODULE_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/directx
) )
target_link_libraries(${MODULE_NAME} PUBLIC core image ${fonts_LIB_DEPENDS}) target_link_libraries(${MODULE_NAME} PUBLIC core geometry image ${fonts_LIB_DEPENDS})
target_compile_definitions(${MODULE_NAME} PRIVATE ${DEFINES}) target_compile_definitions(${MODULE_NAME} PRIVATE ${DEFINES})
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER src) set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER src)

View file

@ -1,5 +1,43 @@
#include "FontGlyph.h" #include "FontGlyph.h"
void GlyphRunOutlines::addToFeature(const Point& point)
{
mFeatures[mFeatures.size() - 1].mPoints.push_back(point);
}
void GlyphRunOutlines::startFeature(const Point& point, bool isFilled)
{
GlyphRunOutline feature;
feature.mFilled = isFilled;
feature.mPoints.push_back(point);
mFeatures.push_back(feature);
}
const std::vector<GlyphRunOutline> GlyphRunOutlines::getFeatures() const
{
return mFeatures;
}
std::string GlyphRunOutlines::toPostScriptPath()
{
std::string path;
for (const auto& feature : mFeatures)
{
if (feature.mPoints.empty())
{
continue;
}
auto last_point = feature.mPoints[0];
path += "M" + std::to_string(last_point.getX()) + " " + std::to_string(last_point.getY()) + " ";
for (std::size_t idx = 1; idx < feature.mPoints.size(); idx++)
{
path += "L" + std::to_string(feature.mPoints[idx].getX()) + " " + std::to_string(feature.mPoints[idx].getY()) + " ";
}
path += "z ";
}
return path;
}
FontGlyph::FontGlyph(unsigned width, unsigned height, int bearingX, int bearingY, FontGlyph::FontGlyph(unsigned width, unsigned height, int bearingX, int bearingY,
int advanceX, std::unique_ptr<Image<unsigned char> > image) int advanceX, std::unique_ptr<Image<unsigned char> > image)
: mImage(std::move(image)), : mImage(std::move(image)),

View file

@ -1,9 +1,31 @@
#pragma once #pragma once
#include <memory> #include "Point.h"
#include "Image.h" #include "Image.h"
#include <memory>
#include <vector>
#include <string>
struct GlyphRunOutline
{
bool mFilled{ true };
std::vector<Point> mPoints;
};
class GlyphRunOutlines
{
public:
void addToFeature(const Point& point);
void startFeature(const Point& point, bool isFilled);
const std::vector<GlyphRunOutline> getFeatures() const;
std::string toPostScriptPath();
private:
std::vector<GlyphRunOutline> mFeatures;
};
class FontGlyph class FontGlyph
{ {

View file

@ -0,0 +1,54 @@
#include "DirectWriteFontEngine.h"
#include "FontGlyph.h"
#include "StringUtils.h"
#include "DirectWriteHelpers.h"
#include <dwrite.h>
void DirectWriteFontEngine::initialize()
{
mIsValid = SUCCEEDED(::DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &mDWriteFactory));
loadFontFaceFromName("Verdana", 16);
}
void DirectWriteFontEngine::initializeOffScreenRenderer()
{
if (mOffScreenRenderer)
{
return;
}
auto pInterface = OffScreenTextRenderer::Create();
pInterface->QueryInterface(IID_IOffScreenTextRenderer, &mOffScreenRenderer);
}
void DirectWriteFontEngine::loadFontFaceFromName(const std::string& fontName, int penSize)
{
mDWriteFactory->CreateTextFormat(StringUtils::convert(fontName).c_str(), nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
penSize, L"en-us", &mTextFormat);
}
std::unique_ptr<FontGlyph> DirectWriteFontEngine::loadGlyph(uint32_t charCode)
{
return nullptr;
}
std::unique_ptr<GlyphRunOutlines> DirectWriteFontEngine::getGlyphRunOutlines(const std::vector<uint32_t>& charCodes)
{
initializeOffScreenRenderer();
std::wstring text;
for (auto code : charCodes)
{
text.push_back(code);
}
auto hr = mDWriteFactory->CreateTextLayout(text.c_str(), text.size(), mTextFormat.Get(), 50.0, 50.0, &mTextLayout);
mTextLayout->Draw(nullptr, mOffScreenRenderer.Get(), 0.0, 0.0);
return mOffScreenRenderer->getGlypyRunOutline();
}

View file

@ -0,0 +1,38 @@
#pragma once
#include "IFontEngine.h"
#include "FontGlyph.h"
#include <wrl.h>
#include <vector>
class GlyphOutlineGeometrySink;
class OffScreenTextRenderer;
struct IDWriteFactory;
struct IDWriteTextFormat;
struct IDWriteTextLayout;
class DirectWriteFontEngine : public IFontEngine
{
public:
void initialize() override;
void loadFontFace(const std::filesystem::path& fontFile, int penSize = 16) override {};
void loadFontFaceFromName(const std::string& fontName, int penSize = 16);
std::unique_ptr<FontGlyph> loadGlyph(uint32_t charCode) override;
std::unique_ptr<GlyphRunOutlines> getGlyphRunOutlines(const std::vector<uint32_t>& charCodes);
private:
void initializeOffScreenRenderer();
bool mIsValid{ false };
Microsoft::WRL::ComPtr<IDWriteFactory> mDWriteFactory;
Microsoft::WRL::ComPtr<IDWriteTextFormat> mTextFormat;
Microsoft::WRL::ComPtr<IDWriteTextLayout> mTextLayout;
Microsoft::WRL::ComPtr<OffScreenTextRenderer> mOffScreenRenderer;
};

View file

@ -0,0 +1,235 @@
#include "DirectWriteHelpers.h"
#include "FileLogger.h"
#include <wrl.h>
GlyphOutlineGeometrySink::GlyphOutlineGeometrySink()
: mGlyphRunOutline(std::make_unique<GlyphRunOutlines>())
{
}
IUnknown* GlyphOutlineGeometrySink::Create()
{
return static_cast<IGlyphOutlineGeometrySink*>(new GlyphOutlineGeometrySink);
}
HRESULT __stdcall GlyphOutlineGeometrySink::QueryInterface(const IID& iid, void** ppv)
{
if (IsEqualIID(iid, __uuidof(IUnknown)))
{
*ppv = static_cast<IGlyphOutlineGeometrySink*>(this);
}
else if (IsEqualIID(iid, IID_ID2D1SimplifiedGeometrySink))
{
*ppv = static_cast<IGlyphOutlineGeometrySink*>(this);
}
else if (IsEqualIID(iid, IID_IGlyphOutlineGeometrySink))
{
*ppv = static_cast<IGlyphOutlineGeometrySink*>(this);
}
else
{
*ppv = nullptr;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
ULONG __stdcall GlyphOutlineGeometrySink::AddRef()
{
return ::InterlockedIncrement(&mRefCount);
}
ULONG __stdcall GlyphOutlineGeometrySink::Release()
{
return 0;
if (::InterlockedDecrement(&mRefCount) == 0)
{
delete this;
return 0;
}
return mRefCount;
}
void __stdcall GlyphOutlineGeometrySink::SetFillMode(D2D1_FILL_MODE fillMode)
{
MLOG_INFO("SetFillMode: " << bool(fillMode == D2D1_FILL_MODE_ALTERNATE));
}
void __stdcall GlyphOutlineGeometrySink::SetSegmentFlags(D2D1_PATH_SEGMENT vertexFlags)
{
MLOG_INFO("SetSegmentFlags");
if (vertexFlags == D2D1_PATH_SEGMENT_NONE)
{
MLOG_INFO("D2D1_PATH_SEGMENT_NONE");
}
else if (vertexFlags == D2D1_PATH_SEGMENT_FORCE_UNSTROKED)
{
MLOG_INFO("D2D1_PATH_SEGMENT_FORCE_UNSTROKED");
}
else if (vertexFlags == D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN)
{
MLOG_INFO("D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN");
}
else if (vertexFlags == D2D1_PATH_SEGMENT_FORCE_DWORD)
{
MLOG_INFO("D2D1_PATH_SEGMENT_FORCE_DWORD");
}
else
{
MLOG_INFO("UKNOWN");
}
}
void __stdcall GlyphOutlineGeometrySink::BeginFigure(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin)
{
MLOG_INFO("BeginFigure, is filled: " << bool(figureBegin == D2D1_FIGURE_BEGIN_FILLED));
mGlyphRunOutline->startFeature(Point(startPoint.x, startPoint.y), figureBegin == D2D1_FIGURE_BEGIN_FILLED);
}
void __stdcall GlyphOutlineGeometrySink::AddLines(_In_reads_(pointsCount) CONST D2D1_POINT_2F* points, UINT32 pointsCount)
{
MLOG_INFO("AddLines");
for (UINT32 idx = 0; idx < pointsCount; idx++)
{
auto point = points[idx];
MLOG_INFO("Adding point: " << point.x << " " << point.y);
mGlyphRunOutline->addToFeature(Point(point.x, point.y));
}
}
void __stdcall GlyphOutlineGeometrySink::AddBeziers(_In_reads_(beziersCount) CONST D2D1_BEZIER_SEGMENT* beziers, UINT32 beziersCount)
{
MLOG_INFO("AddBeziers");
}
void __stdcall GlyphOutlineGeometrySink::EndFigure(D2D1_FIGURE_END figureEnd)
{
MLOG_INFO("EndFigure");
}
HRESULT __stdcall GlyphOutlineGeometrySink::Close()
{
return S_OK;
}
std::unique_ptr<GlyphRunOutlines> GlyphOutlineGeometrySink::getGlypyRunOutline()
{
return std::move(mGlyphRunOutline);
}
IUnknown* OffScreenTextRenderer::Create()
{
return static_cast<IOffScreenTextRenderer*>(new OffScreenTextRenderer);
}
HRESULT __stdcall OffScreenTextRenderer::QueryInterface(const IID& iid, void** ppv)
{
if (IsEqualIID(iid, __uuidof(IUnknown)))
{
*ppv = static_cast<IOffScreenTextRenderer*>(this);
}
else if (IsEqualIID(iid, __uuidof(IDWritePixelSnapping)))
{
*ppv = static_cast<IOffScreenTextRenderer*>(this);
}
else if (IsEqualIID(iid, __uuidof(IDWriteTextRenderer)))
{
*ppv = static_cast<IOffScreenTextRenderer*>(this);
}
else if (IsEqualIID(iid, IID_IOffScreenTextRenderer))
{
*ppv = static_cast<IOffScreenTextRenderer*>(this);
}
else
{
*ppv = nullptr;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
ULONG __stdcall OffScreenTextRenderer::AddRef()
{
return ::InterlockedIncrement(&mRefCount);
}
ULONG __stdcall OffScreenTextRenderer::Release()
{
return 0;
if (::InterlockedDecrement(&mRefCount) == 0)
{
delete this;
return 0;
}
return mRefCount;
}
HRESULT __stdcall OffScreenTextRenderer::DrawGlyphRun(_In_opt_ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode,
_In_ DWRITE_GLYPH_RUN const* glyphRun, _In_ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, _In_opt_ IUnknown* clientDrawingEffect)
{
auto pInterface = GlyphOutlineGeometrySink::Create();
Microsoft::WRL::ComPtr<GlyphOutlineGeometrySink> sink;
pInterface->QueryInterface(IID_IGlyphOutlineGeometrySink, &sink);
auto hr = glyphRun->fontFace->GetGlyphRunOutline(
glyphRun->fontEmSize,
glyphRun->glyphIndices,
glyphRun->glyphAdvances,
glyphRun->glyphOffsets,
glyphRun->glyphCount,
glyphRun->isSideways,
glyphRun->bidiLevel % 2,
sink.Get()
);
sink->Close();
mGlyphRunOutline = sink->getGlypyRunOutline();
return S_OK;
}
HRESULT __stdcall OffScreenTextRenderer::DrawUnderline(_In_opt_ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, _In_ DWRITE_UNDERLINE const* underline,
_In_opt_ IUnknown* clientDrawingEffect)
{
return S_OK;
}
HRESULT __stdcall OffScreenTextRenderer::DrawStrikethrough(_In_opt_ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, _In_ DWRITE_STRIKETHROUGH const* strikethrough,
_In_opt_ IUnknown* clientDrawingEffect)
{
return S_OK;
}
HRESULT __stdcall OffScreenTextRenderer::DrawInlineObject(_In_opt_ void* clientDrawingContext, FLOAT originX, FLOAT originY, _In_ IDWriteInlineObject* inlineObject,
BOOL isSideways, BOOL isRightToLeft, _In_opt_ IUnknown* clientDrawingEffect)
{
return S_OK;
}
HRESULT __stdcall OffScreenTextRenderer::IsPixelSnappingDisabled(_In_opt_ void* clientDrawingContext, _Out_ BOOL* isDisabled)
{
*isDisabled = FALSE;
return S_OK;
}
HRESULT __stdcall OffScreenTextRenderer::GetCurrentTransform(_In_opt_ void* clientDrawingContext, _Out_ DWRITE_MATRIX* transform)
{
*transform = DWRITE_MATRIX(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
return S_OK;
}
HRESULT __stdcall OffScreenTextRenderer::GetPixelsPerDip(_In_opt_ void* clientDrawingContext, _Out_ FLOAT* pixelsPerDip)
{
*pixelsPerDip = 1.0;
return S_OK;
}
std::unique_ptr<GlyphRunOutlines> OffScreenTextRenderer::getGlypyRunOutline()
{
return std::move(mGlyphRunOutline);
}

View file

@ -0,0 +1,90 @@
#pragma once
#include "FontGlyph.h"
#include <d2d1_1.h>
#include <dwrite.h>
#include <memory>
static const IID IID_IGlyphOutlineGeometrySink = { 0xaaf6ab8d, 0xd6cf, 0x4a3b, { 0xa6, 0x7a, 0x12, 0xf1, 0x42, 0xe0, 0x6b, 0xb9 } };
class IGlyphOutlineGeometrySink : public IDWriteGeometrySink
{
public:
virtual std::unique_ptr<GlyphRunOutlines> getGlypyRunOutline() = 0;
};
class GlyphOutlineGeometrySink :public IGlyphOutlineGeometrySink
{
public:
GlyphOutlineGeometrySink();
// IUnknown
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
static IUnknown* Create();
// IDWriteGeometrySink aka ID2D1SimplifiedGeometrySink
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE fillMode);
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT vertexFlags);
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin);
STDMETHOD_(void, AddLines)(_In_reads_(pointsCount) CONST D2D1_POINT_2F* points, UINT32 pointsCount);
STDMETHOD_(void, AddBeziers)(_In_reads_(beziersCount) CONST D2D1_BEZIER_SEGMENT* beziers, UINT32 beziersCount);
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END figureEnd);
STDMETHOD(Close)();
// IGlyphOutlineGeometrySink
std::unique_ptr<GlyphRunOutlines> getGlypyRunOutline() override;
private:
std::unique_ptr<GlyphRunOutlines> mGlyphRunOutline;
long mRefCount{ 0 };
};
static const IID IID_IOffScreenTextRenderer = { 0xa43b0c49, 0x6080, 0x4731, { 0xa8, 0x72, 0x61, 0x50, 0x47, 0x3, 0x78, 0x10 } };
class IOffScreenTextRenderer : public IDWriteTextRenderer
{
virtual std::unique_ptr<GlyphRunOutlines> getGlypyRunOutline() = 0;
};
class OffScreenTextRenderer :public IOffScreenTextRenderer
{
public:
// IUnknown
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
static IUnknown* Create();
STDMETHOD(DrawGlyphRun)(_In_opt_ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode,
_In_ DWRITE_GLYPH_RUN const* glyphRun, _In_ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, _In_opt_ IUnknown* clientDrawingEffect);
STDMETHOD(DrawUnderline)(_In_opt_ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, _In_ DWRITE_UNDERLINE const* underline,
_In_opt_ IUnknown* clientDrawingEffect);
STDMETHOD(DrawStrikethrough)(_In_opt_ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, _In_ DWRITE_STRIKETHROUGH const* strikethrough,
_In_opt_ IUnknown* clientDrawingEffect);
STDMETHOD(DrawInlineObject)(_In_opt_ void* clientDrawingContext, FLOAT originX, FLOAT originY, _In_ IDWriteInlineObject* inlineObject,
BOOL isSideways, BOOL isRightToLeft, _In_opt_ IUnknown* clientDrawingEffect);
STDMETHOD(IsPixelSnappingDisabled)(_In_opt_ void* clientDrawingContext, _Out_ BOOL* isDisabled);
STDMETHOD(GetCurrentTransform)(_In_opt_ void* clientDrawingContext, _Out_ DWRITE_MATRIX* transform);
STDMETHOD(GetPixelsPerDip)(_In_opt_ void* clientDrawingContext, _Out_ FLOAT* pixelsPerDip);
std::unique_ptr<GlyphRunOutlines> getGlypyRunOutline() override;
private:
std::unique_ptr<GlyphRunOutlines> mGlyphRunOutline;
long mRefCount{ 0 };
};

View file

@ -17,7 +17,7 @@ SvgDocument::SvgDocument()
setRoot(std::move(root)); setRoot(std::move(root));
} }
void SvgDocument::setViewBox(unsigned x, unsigned y, unsigned w, unsigned h) void SvgDocument::setViewBox(double x, double y, double w, double h)
{ {
auto viewbox = std::make_unique<XmlAttribute>("viewBox"); auto viewbox = std::make_unique<XmlAttribute>("viewBox");
std::stringstream sstr; std::stringstream sstr;

View file

@ -6,5 +6,5 @@ class SvgDocument : public XmlDocument
{ {
public: public:
SvgDocument(); SvgDocument();
void setViewBox(unsigned x, unsigned y, unsigned w, unsigned h); void setViewBox(double x, double y, double w, double h);
}; };

View file

@ -104,3 +104,23 @@ void SvgPolyline::setPoints(const std::vector<DiscretePoint>& locs)
addAttribute(std::move(points)); addAttribute(std::move(points));
} }
SvgPath::SvgPath()
: SvgShapeElement("path")
{
}
void SvgPath::setPath(const std::string& mPath)
{
auto path = std::make_unique<XmlAttribute>("d");
path->setValue(mPath);
addAttribute(std::move(path));
}
void SvgPath::setFillRule(const std::string& fillRule)
{
auto rule = std::make_unique<XmlAttribute>("fill-rule");
rule->setValue(fillRule);
addAttribute(std::move(rule));
}

View file

@ -42,3 +42,12 @@ public:
void setPoints(const std::vector<DiscretePoint>& loc); void setPoints(const std::vector<DiscretePoint>& loc);
}; };
class SvgPath : public SvgShapeElement
{
public:
SvgPath();
void setPath(const std::string& mPath);
void setFillRule(const std::string& fillRule);
};

View file

@ -7,6 +7,10 @@ if(UNIX)
fonts/TestFreeTypeFontEngine.cpp fonts/TestFreeTypeFontEngine.cpp
) )
endif() endif()
else()
set(PLATFORM_UNIT_TEST_FILES
fonts/TestDirectWriteFontEngine.cpp
)
endif() endif()

View file

@ -0,0 +1,36 @@
#include "TestFramework.h"
#include "TestUtils.h"
#include "DirectWriteHelpers.h"
#include "DirectWriteFontEngine.h"
#include "FileLogger.h"
#include "SvgDocument.h"
#include "SvgWriter.h"
#include "SvgShapeElements.h"
#include "File.h"
TEST_CASE(TestDirectWriteFontEngine, "fonts")
{
DirectWriteFontEngine font_engine;
font_engine.initialize();
auto glyph_run_outlines = font_engine.getGlyphRunOutlines({ 66 });
auto path = glyph_run_outlines->toPostScriptPath();
auto doc = std::make_unique<SvgDocument>();
auto element = std::make_unique<SvgPath>();
element->setPath(path);
element->setFillRule("evenodd");
doc->getRoot()->addChild(std::move(element));
doc->setViewBox(-20.0, -20.0, 40.0, 40.0);
auto writer = std::make_unique<SvgWriter>();
auto doc_string = writer->toString(doc.get());
File file(TestUtils::getTestOutputDir(__FILE__) / "out.svg");
file.writeText(doc_string);
}