Files
react-native-svg/windows/RNSVG/RenderableView.cpp
Andrew Coates d02c997352 fix: react-native-windows implementation for new architecture (#2527)
# Summary
There are two main things going on with the PR. Both involve a reworking
of the new arch implementation of rn-svg for windows.
The current implementation attempts to implement a svg renderer from
scratch. There are numerous edge cases that it wasn't handling
correctly, and the performance had some serious issues. This
implementation switches to use the svg rendering path built into
Direct2D. This brings significant performance improvements.

The 2nd issue is there have been various breaking changes in
react-native-windows for how new arch native components are implemented.
This brings the rn-svg implementation in line with those latest changes.

## Test Plan

Primary testing right now is loading up the example app in the repo.
New arch on react-native-windows is still in somewhat early days - so
there are not really current users of this code. I am integrating this
code into Microsoft Office, where I have tested some scenarios. But we
will get expanded testing as we roll out the new arch. I expect there to
be some follow-ups as we expand our usage. The version of rn-svg before
this PR doesn't build with the latest new arch react-native-windows
versions. - So its hard to get worse than that.

### What's required for testing (prerequisites)?

### What are the steps to reproduce (after prerequisites)?

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    N/A  |
| MacOS   |    N/A      |
| Android |    N/A      |
| Web     |          |

## Checklist

- [x] I have tested this on a device and a simulator
- [ ] I added documentation in `README.md`
- [ ] I updated the typed files (typescript)
- [ ] I added a test for the API in the `__tests__` folder
2024-11-15 12:17:12 +01:00

858 lines
31 KiB
C++

#include "pch.h"
#include "RenderableView.h"
#if __has_include("RenderableView.g.cpp")
#include "RenderableView.g.cpp"
#endif
#include "JSValueXaml.h"
#include "SvgView.h"
#include "Utils.h"
using namespace winrt;
using namespace Microsoft::ReactNative;
namespace winrt::RNSVG::implementation {
#ifdef USE_FABRIC
SvgNodeCommonProps::SvgNodeCommonProps(
const winrt::Microsoft::ReactNative::ViewProps &props)
: m_props(props) {}
void SvgNodeCommonProps::SetProp(
uint32_t hash,
winrt::hstring propName,
winrt::Microsoft::ReactNative::IJSValueReader value) noexcept {
winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this);
}
SvgRenderableCommonProps::SvgRenderableCommonProps(
const winrt::Microsoft::ReactNative::ViewProps &props)
: base_type(props) {}
void SvgRenderableCommonProps::SetProp(
uint32_t hash,
winrt::hstring propName,
winrt::Microsoft::ReactNative::IJSValueReader value) noexcept {
winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this);
}
void RenderableView::MountChildComponentView(
const winrt::Microsoft::ReactNative::ComponentView&,
const winrt::Microsoft::ReactNative::MountChildComponentViewArgs& args) noexcept
{
const RNSVG::RenderableView &view{*this};
if (auto userData = args.Child().UserData()) {
const auto &group{view.try_as<RNSVG::GroupView>()};
const auto &child{userData.try_as<IRenderable>()};
m_children.InsertAt(args.Index(), child);
userData.as<IRenderableFabric>().SvgParent(*this);
assert(group && child);
if (group && child) {
child.MergeProperties(*this);
if (child.IsResponsible() && !IsResponsible()) {
IsResponsible(true);
}
if (auto const &root{SvgRoot()}) {
root.Invalidate();
}
}
}
}
void RenderableView::UnmountChildComponentView(
const winrt::Microsoft::ReactNative::ComponentView &,
const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept {
if (auto userData = args.Child().UserData()) {
const RNSVG::RenderableView &view{*this};
const auto &group{view.try_as<RNSVG::GroupView>()};
const auto &child{userData.try_as<IRenderable>()};
userData.as<IRenderableFabric>().SvgParent(nullptr);
if (group && child) {
if (!IsUnloaded()) {
child.Unload();
}
m_children.RemoveAt(args.Index());
if (auto const &root{SvgRoot()}) {
root.Invalidate();
}
}
}
}
void RenderableView::UpdateProps(
const winrt::Microsoft::ReactNative::ComponentView & /*view*/,
const winrt::Microsoft::ReactNative::IComponentProps &props,
const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept {
if (!props && !oldProps)
return;
UpdateProperties(props, oldProps);
}
void RenderableView::UpdateProperties(
const winrt::Microsoft::ReactNative::IComponentProps &props,
const winrt::Microsoft::ReactNative::IComponentProps &oldProps,
bool forceUpdate,
bool invalidate) noexcept {
auto renderableProps = props.as<SvgRenderableCommonProps>();
auto oldRenderableProps =
oldProps ? oldProps.as<SvgRenderableCommonProps>() : nullptr;
auto const &parent{SvgParent().try_as<RNSVG::RenderableView>()};
// propList
/*
auto const &propList{propertyMap.find("propList")};
if (propList != propertyMap.end()) {
m_propList.clear();
auto const &propValue{(*propList).second};
for (auto const &item : propValue.AsArray()) {
m_propList.push_back(Utils::JSValueAsString(item));
}
}
*/
/*******************************/
/* REACT_SVG_NODE_COMMON_PROPS */
/*******************************/
// name
// not a prop we want to propagate to child elements so we only set it when forceUpdate = true
if (forceUpdate && (!oldRenderableProps || renderableProps->name != oldRenderableProps->name)) {
if (parent) {
SvgRoot().Templates().Remove(m_id);
}
m_id = winrt::to_hstring(Utils::JSValueAsString(renderableProps->name));
if (parent) {
SaveDefinition();
}
}
// opacity
// not a prop we want to propagate to child elements so we only set it when forceUpdate = true
if (forceUpdate && (!oldRenderableProps || renderableProps->opacity != oldRenderableProps->opacity)) {
m_opacity = Utils::JSValueAsFloat(renderableProps->opacity, 1.0f);
}
// matrix
if (!oldRenderableProps || renderableProps->matrix != oldRenderableProps->matrix) {
if (forceUpdate) {
m_transformMatrix = renderableProps->matrix != std::nullopt
? Numerics::float3x2(
renderableProps->matrix->at(0),
renderableProps->matrix->at(1),
renderableProps->matrix->at(2),
renderableProps->matrix->at(3),
renderableProps->matrix->at(4),
renderableProps->matrix->at(5))
: (parent ? parent.SvgTransform() : Numerics::float3x2::identity());
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::Matrix] = renderableProps->matrix != std::nullopt;
}
}
}
// mask - not implemented
//if (!oldRenderableProps || renderableProps->mask != oldRenderableProps->mask) {
// m_maskId = to_hstring(Utils::JSValueAsString(renderableProps->mask));
//}
// markerStart - not implemented
//if (!oldRenderableProps || renderableProps->markerStart != oldRenderableProps->markerStart) {
// m_markerStart = to_hstring(Utils::JSValueAsString(renderableProps->markerStart));
//}
// markerMid - not implemented
//if (!oldRenderableProps || renderableProps->markerMid != oldRenderableProps->markerMid) {
// m_markerMid = to_hstring(Utils::JSValueAsString(renderableProps->markerMid));
//}
// markerEnd - not implemented
//if (!oldRenderableProps || renderableProps->markerEnd != oldRenderableProps->markerEnd) {
// m_markerEnd = to_hstring(Utils::JSValueAsString(renderableProps->markerEnd));
//}
// clipPath
// not a prop we want to propagate to child elements so we only set it when forceUpdate = true
if (forceUpdate && (!oldRenderableProps || renderableProps->clipPath != oldRenderableProps->clipPath)) {
m_clipPathId = to_hstring(Utils::JSValueAsString(renderableProps->clipPath));
}
// responsible
if (!oldRenderableProps || renderableProps->responsible != oldRenderableProps->responsible) {
m_isResponsible = renderableProps->responsible != std::nullopt ? *renderableProps->responsible : false;
}
// display - not implemented
//if (!oldRenderableProps || renderableProps->display != oldRenderableProps->display) {
// m_display = Utils::JSValueAsString(renderableProps->display);
//}
// pointerEvents - not implemented
/*
if (!oldRenderableProps || renderableProps->pointerEvents != oldRenderableProps->pointerEvents) {
m_pointerEvents = Utils::JSValueAsString(renderableProps->pointerEvents);
}
*/
/*************************************/
/* REACT_SVG_RENDERABLE_COMMON_PROPS */
/*************************************/
// fill
if (!oldRenderableProps || renderableProps->fill != oldRenderableProps->fill) {
bool fillSet{
renderableProps->propList &&
std::find(renderableProps->propList->begin(), renderableProps->propList->end(), "fill") !=
renderableProps->propList->end()};
if (forceUpdate || (fillSet && !m_propSetMap[RNSVG::BaseProp::Fill])) {
winrt::Microsoft::ReactNative::Color fallbackColor{winrt::Microsoft::ReactNative::Color::Black()};
if (renderableProps->fill == std::nullopt && fillSet) {
fallbackColor = winrt::Microsoft::ReactNative::Color::Transparent();
} else if (parent) {
fallbackColor = parent.Fill();
}
if (!m_fillBrushId.empty()) {
m_fillBrushId.clear();
}
SetColor(renderableProps->fill, fallbackColor, "fill");
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
m_propSetMap[RNSVG::BaseProp::Fill] = fillSet;
}
}
// fillOpacity
if (!oldRenderableProps || renderableProps->fillOpacity != oldRenderableProps->fillOpacity) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::FillOpacity]) {
float fallbackValue{parent ? parent.FillOpacity() : 1.0f};
m_fillOpacity = Utils::JSValueAsFloat(renderableProps->fillOpacity, fallbackValue);
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::FillOpacity] = renderableProps->fillOpacity != std::nullopt;
}
}
// fillRule
if (!oldRenderableProps || renderableProps->fillRule != oldRenderableProps->fillRule) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::FillRule]) {
m_fillRule = renderableProps->fillRule != std::nullopt ? *renderableProps->fillRule
: (parent ? parent.FillRule() : RNSVG::FillRule::NonZero);
}
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::FillRule] = renderableProps->fillRule != std::nullopt;
}
}
// stroke
if (!oldRenderableProps || renderableProps->stroke != oldRenderableProps->stroke) {
bool strokeSet{
renderableProps->propList &&
std::find(renderableProps->propList->begin(), renderableProps->propList->end(), "stroke") !=
renderableProps->propList->end()};
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::Stroke]) {
winrt::Microsoft::ReactNative::Color fallbackColor{
((parent && !strokeSet) ? parent.Stroke() : winrt::Microsoft::ReactNative::Color::Transparent())};
if (!m_strokeBrushId.empty()) {
m_strokeBrushId.clear();
}
SetColor(renderableProps->stroke, fallbackColor, "stroke");
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
m_propSetMap[RNSVG::BaseProp::Stroke] = strokeSet;
}
}
// strokeOpacity
if (!oldRenderableProps || renderableProps->strokeOpacity != oldRenderableProps->strokeOpacity) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeOpacity]) {
float fallbackValue{parent ? parent.StrokeOpacity() : 1.0f};
m_strokeOpacity = Utils::JSValueAsFloat(renderableProps->strokeOpacity, fallbackValue);
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeOpacity] = renderableProps->strokeOpacity != std::nullopt;
}
}
// strokeWidth
if (!oldRenderableProps || renderableProps->strokeWidth != oldRenderableProps->strokeWidth) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeWidth]) {
m_strokeWidth = (renderableProps->strokeWidth != std::nullopt)
? *renderableProps->strokeWidth
: (parent ? parent.StrokeWidth() : RNSVG::SVGLength{1.0f, RNSVG::LengthType::Pixel});
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeWidth] = renderableProps->strokeWidth != std::nullopt;
}
}
// strokeLinecap
if (!oldRenderableProps || renderableProps->strokeLinecap != oldRenderableProps->strokeLinecap) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeLineCap]) {
m_strokeLineCap = renderableProps->strokeLinecap != std::nullopt
? *renderableProps->strokeLinecap
: (parent ? parent.StrokeLineCap() : RNSVG::LineCap::Butt);
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeLineCap] = renderableProps->strokeLinecap != std::nullopt;
}
}
// strokeLinejoin
if (!oldRenderableProps || renderableProps->strokeLinejoin != oldRenderableProps->strokeLinejoin) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeLineJoin]) {
m_strokeLineJoin = renderableProps->strokeLinejoin != std::nullopt
? *renderableProps->strokeLinejoin
: (parent ? parent.StrokeLineJoin() : RNSVG::LineJoin::Miter);
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeLineJoin] = renderableProps->strokeLinejoin != std::nullopt;
}
}
// strokeDasharray
if (!oldRenderableProps || renderableProps->strokeDasharray != oldRenderableProps->strokeDasharray) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeDashArray]) {
if (renderableProps->strokeDasharray != std::nullopt) {
m_strokeDashArray.Clear();
for (auto const &item : *renderableProps->strokeDasharray) {
m_strokeDashArray.Append(item);
}
} else {
m_strokeDashArray = (parent ? parent.StrokeDashArray() : winrt::single_threaded_vector<RNSVG::SVGLength>());
}
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeDashArray] = renderableProps->strokeDasharray != std::nullopt;
}
}
}
// strokeDashoffset
if (!oldRenderableProps || renderableProps->strokeDashoffset != oldRenderableProps->strokeDashoffset) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeDashOffset]) {
float fallbackValue{parent ? parent.StrokeDashOffset() : 0.0f};
m_strokeDashOffset = Utils::JSValueAsFloat(renderableProps->strokeDashoffset, fallbackValue);
}
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeDashOffset] = renderableProps->strokeDashoffset != std::nullopt;
}
}
// strokeMiterlimit
if (!oldRenderableProps || renderableProps->strokeMiterlimit != oldRenderableProps->strokeMiterlimit) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::StrokeMiterLimit]) {
float fallbackValue{parent ? parent.StrokeMiterLimit() : 0.0f};
m_strokeMiterLimit = Utils::JSValueAsFloat(renderableProps->strokeMiterlimit, fallbackValue);
}
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::StrokeMiterLimit] = renderableProps->strokeMiterlimit != std::nullopt;
}
}
// vectorEffect - not implemented
/*
if (!oldRenderableProps || renderableProps->vectorEffect != oldRenderableProps->vectorEffect) {
if (forceUpdate || !m_propSetMap[RNSVG::BaseProp::VectorEffect]) {
m_vectorEffect = renderableProps->vectorEffect != std::nullopt
? *renderableProps->vectorEffect
: (parent ? parent.VectorEffect() : RNSVG::VectorEffect::None);
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate) {
// If the optional is null, that generally means the prop was deleted
m_propSetMap[RNSVG::BaseProp::VectorEffect] = renderableProps->vectorEffect != std::nullopt;
}
}
*/
m_recreateResources = true;
if (invalidate && SvgParent()) {
SvgRoot().Invalidate();
}
}
const winrt::Windows::Foundation::Collections::IVector<IRenderable>& RenderableView::Children() const noexcept {
return m_children;
}
#else
void RenderableView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) {
const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)};
auto const &parent{SvgParent().try_as<RNSVG::RenderableView>()};
auto const &propList{propertyMap.find("propList")};
if (propList != propertyMap.end()) {
m_propList.clear();
auto const &propValue{(*propList).second};
for (auto const &item : propValue.AsArray()) {
m_propList.push_back(Utils::JSValueAsString(item));
}
}
bool fillSet{std::find(m_propList.begin(), m_propList.end(), "fill") != m_propList.end()};
bool strokeSet{std::find(m_propList.begin(), m_propList.end(), "stroke") != m_propList.end()};
for (auto const &pair : propertyMap) {
auto const &propertyName{pair.first};
auto const &propertyValue{pair.second};
auto prop{RNSVG::BaseProp::Unknown};
// name is not a prop we want to propagate to child elements
// so we only set it when forceUpdate = true
if (propertyName == "name" && forceUpdate) {
if (parent) {
SvgRoot().Templates().Remove(m_id);
}
m_id = to_hstring(Utils::JSValueAsString(propertyValue));
if (parent) {
SaveDefinition();
}
} else if (propertyName == "strokeWidth") {
prop = RNSVG::BaseProp::StrokeWidth;
if (forceUpdate || !m_propSetMap[prop]) {
auto const &fallbackValue{parent ? parent.StrokeWidth() : RNSVG::SVGLength(1.0f, RNSVG::LengthType::Pixel)};
m_strokeWidth = Utils::JSValueAsSVGLength(propertyValue, fallbackValue);
}
} else if (propertyName == "strokeOpacity") {
prop = RNSVG::BaseProp::StrokeOpacity;
if (forceUpdate || !m_propSetMap[prop]) {
float fallbackValue{parent ? parent.StrokeOpacity() : 1.0f};
m_strokeOpacity = Utils::JSValueAsFloat(propertyValue, fallbackValue);
}
} else if (propertyName == "fillOpacity") {
prop = RNSVG::BaseProp::FillOpacity;
if (forceUpdate || !m_propSetMap[prop]) {
float fallbackValue{parent ? parent.FillOpacity() : 1.0f};
m_fillOpacity = Utils::JSValueAsFloat(propertyValue, fallbackValue);
}
} else if (propertyName == "stroke") {
prop = RNSVG::BaseProp::Stroke;
if (forceUpdate || !m_propSetMap[prop]) {
Windows::UI::Color fallbackColor{(parent && !strokeSet) ? parent.Stroke() : Windows::UI::Colors::Transparent()};
if (!m_strokeBrushId.empty()) {
m_strokeBrushId.clear();
}
SetColor(propertyValue.AsObject(), fallbackColor, propertyName);
}
} else if (propertyName == "fill") {
prop = RNSVG::BaseProp::Fill;
if (forceUpdate || !m_propSetMap[prop]) {
Windows::UI::Color fallbackColor{Windows::UI::Colors::Black()};
if (propertyValue.IsNull() && fillSet) {
fallbackColor = Windows::UI::Colors::Transparent();
} else if (parent) {
fallbackColor = parent.Fill();
}
if (!m_fillBrushId.empty()) {
m_fillBrushId.clear();
}
SetColor(propertyValue.AsObject(), fallbackColor, propertyName);
}
} else if (propertyName == "strokeLinecap") {
prop = RNSVG::BaseProp::StrokeLineCap;
if (forceUpdate || !m_propSetMap[prop]) {
if (propertyValue.IsNull()) {
m_strokeLineCap = parent.StrokeLineCap();
} else {
m_strokeLineCap = static_cast<RNSVG::LineCap>(propertyValue.AsInt32());
}
}
} else if (propertyName == "strokeLinejoin") {
prop = RNSVG::BaseProp::StrokeLineJoin;
if (forceUpdate || !m_propSetMap[prop]) {
if (propertyValue.IsNull()) {
m_strokeLineJoin = parent.StrokeLineJoin();
} else {
m_strokeLineJoin = static_cast<RNSVG::LineJoin>(propertyValue.AsInt32());
}
}
} else if (propertyName == "fillRule") {
prop = RNSVG::BaseProp::FillRule;
if (forceUpdate || !m_propSetMap[prop]) {
if (propertyValue.IsNull()) {
m_fillRule = parent.FillRule();
} else {
m_fillRule = static_cast<RNSVG::FillRule>(propertyValue.AsInt32());
}
}
} else if (propertyName == "strokeDashoffset") {
prop = RNSVG::BaseProp::StrokeDashOffset;
if (forceUpdate || !m_propSetMap[prop]) {
float fallbackValue{parent ? parent.StrokeDashOffset() : 0.0f};
m_strokeDashOffset = Utils::JSValueAsFloat(propertyValue, fallbackValue);
}
} else if (propertyName == "strokeMiterlimit") {
prop = RNSVG::BaseProp::StrokeMiterLimit;
if (forceUpdate || !m_propSetMap[prop]) {
float fallbackValue{parent ? parent.StrokeMiterLimit() : 0.0f};
m_strokeMiterLimit = Utils::JSValueAsFloat(propertyValue, fallbackValue);
}
} else if (propertyName == "strokeDasharray") {
prop = RNSVG::BaseProp::StrokeDashArray;
if (forceUpdate || !m_propSetMap[prop]) {
if (propertyValue.IsNull()) {
m_strokeDashArray = parent.StrokeDashArray();
} else {
auto const &asArray = propertyValue.AsArray();
if (!asArray.empty() && (asArray.size() % 2 == 0)) {
m_strokeDashArray.Clear();
for (auto const &item : asArray) {
m_strokeDashArray.Append(item.To<RNSVG::SVGLength>());
}
}
}
}
} else if (propertyName == "matrix") {
prop = RNSVG::BaseProp::Matrix;
if (forceUpdate) {
Numerics::float3x2 fallbackValue{parent ? parent.SvgTransform() : Numerics::make_float3x2_rotation(0)};
m_transformMatrix = Utils::JSValueAsTransform(propertyValue, fallbackValue);
}
} else if (propertyName == "opacity" && forceUpdate) {
m_opacity = Utils::JSValueAsFloat(propertyValue, 1.0f);
} else if (propertyName == "clipPath") {
m_clipPathId = to_hstring(Utils::JSValueAsString(propertyValue));
} else if (propertyName == "responsible") {
m_isResponsible = propertyValue.AsBoolean();
}
// forceUpdate = true means the property is being set on an element
// instead of being inherited from the parent.
if (forceUpdate && (prop != RNSVG::BaseProp::Unknown)) {
// If the propertyValue is null, that generally means the prop was deleted
bool propSet{!propertyValue.IsNull()};
// The exception being Fill and Stroke due to 'none' coming through as null
if (prop == RNSVG::BaseProp::Fill) {
propSet = fillSet;
} else if (prop == RNSVG::BaseProp::Stroke) {
propSet = strokeSet;
}
m_propSetMap[prop] = propSet;
}
}
m_recreateResources = true;
if (invalidate && SvgParent()) {
SvgRoot().Invalidate();
}
}
#endif
void RenderableView::SaveDefinition() {
if (m_id != L"") {
SvgRoot().Templates().Insert(m_id, *this);
}
}
void RenderableView::Draw(RNSVG::D2DDeviceContext const &context, Size const &size) {
if (m_recreateResources) {
CreateGeometry(context);
}
if (!Geometry()) {
return;
}
com_ptr<ID2D1Geometry> geometry{get_self<D2DGeometry>(m_geometry)->Get()};
com_ptr<ID2D1DeviceContext> deviceContext{get_self<D2DDeviceContext>(context)->Get()};
D2D1_MATRIX_3X2_F transform{D2DHelpers::GetTransform(deviceContext.get())};
if (m_propSetMap[RNSVG::BaseProp::Matrix]) {
deviceContext->SetTransform(D2DHelpers::AsD2DTransform(SvgTransform()) * transform);
}
com_ptr<ID2D1Factory> factory;
deviceContext->GetFactory(factory.put());
com_ptr<ID2D1GeometryGroup> geometryGroup;
ID2D1Geometry *geometries[] = {geometry.get()};
check_hresult(factory->CreateGeometryGroup(D2DHelpers::GetFillRule(FillRule()), geometries, 1, geometryGroup.put()));
geometry = geometryGroup;
com_ptr<ID2D1Geometry> clipPathGeometry;
if (ClipPathGeometry(context)) {
clipPathGeometry = get_self<D2DGeometry>(ClipPathGeometry(context))->Get();
}
D2DHelpers::PushOpacityLayer(deviceContext.get(), clipPathGeometry.get(), m_opacity);
if (FillOpacity()) {
D2DHelpers::PushOpacityLayer(deviceContext.get(), clipPathGeometry.get(), FillOpacity());
auto fill{Utils::GetCanvasBrush(FillBrushId(), Fill(), SvgRoot(), geometry, context)};
deviceContext->FillGeometry(geometry.get(), fill.get());
deviceContext->PopLayer();
}
if (StrokeOpacity()) {
D2DHelpers::PushOpacityLayer(deviceContext.get(), clipPathGeometry.get(), StrokeOpacity());
D2D1_CAP_STYLE capStyle{D2DHelpers::GetLineCap(m_strokeLineCap)};
D2D1_LINE_JOIN lineJoin{D2DHelpers::GetLineJoin(m_strokeLineJoin)};
D2D1_STROKE_STYLE_PROPERTIES strokeStyleProperties;
strokeStyleProperties.startCap = capStyle;
strokeStyleProperties.endCap = capStyle;
strokeStyleProperties.dashCap = capStyle;
strokeStyleProperties.lineJoin = lineJoin;
strokeStyleProperties.dashOffset = StrokeDashOffset();
strokeStyleProperties.miterLimit = StrokeMiterLimit();
strokeStyleProperties.dashStyle = D2D1_DASH_STYLE_SOLID;
float canvasDiagonal{Utils::GetCanvasDiagonal(size)};
float strokeWidth{Utils::GetAbsoluteLength(StrokeWidth(), canvasDiagonal)};
float *dashArray = nullptr;
if (StrokeDashArray().Size() > 0) {
strokeStyleProperties.dashStyle = D2D1_DASH_STYLE_CUSTOM;
m_adjustedStrokeDashArray = Utils::GetAdjustedStrokeArray(StrokeDashArray(), strokeWidth, canvasDiagonal);
dashArray = &m_adjustedStrokeDashArray[0];
}
com_ptr<ID2D1StrokeStyle> strokeStyle;
check_hresult(factory->CreateStrokeStyle(strokeStyleProperties, dashArray, m_strokeDashArray.Size(), strokeStyle.put()));
auto const stroke{Utils::GetCanvasBrush(StrokeBrushId(), Stroke(), SvgRoot(), geometry, context)};
deviceContext->DrawGeometry(geometry.get(), stroke.get(), strokeWidth, strokeStyle.get());
deviceContext->PopLayer();
}
deviceContext->PopLayer();
deviceContext->SetTransform(transform);
}
void RenderableView::MergeProperties(RNSVG::IRenderable const &other) {
auto view{other.try_as<RNSVG::RenderableView>()};
for (auto const &prop : m_propSetMap) {
if (!prop.second && view) {
switch (prop.first) {
case RNSVG::BaseProp::Fill:
m_fill = view.Fill();
m_fillBrushId = view.FillBrushId();
break;
case RNSVG::BaseProp::FillOpacity:
m_fillOpacity = view.FillOpacity();
break;
case RNSVG::BaseProp::FillRule:
m_fillRule = view.FillRule();
break;
case RNSVG::BaseProp::Stroke:
m_stroke = view.Stroke();
m_strokeBrushId = view.StrokeBrushId();
break;
case RNSVG::BaseProp::StrokeOpacity:
m_strokeOpacity = view.StrokeOpacity();
break;
case RNSVG::BaseProp::StrokeWidth:
m_strokeWidth = view.StrokeWidth();
break;
case RNSVG::BaseProp::StrokeMiterLimit:
m_strokeMiterLimit = view.StrokeMiterLimit();
break;
case RNSVG::BaseProp::StrokeDashOffset:
m_strokeDashOffset = view.StrokeDashOffset();
break;
case RNSVG::BaseProp::StrokeDashArray:
m_strokeDashArray = view.StrokeDashArray();
break;
case RNSVG::BaseProp::StrokeLineCap:
m_strokeLineCap = view.StrokeLineCap();
break;
case RNSVG::BaseProp::StrokeLineJoin:
m_strokeLineJoin = view.StrokeLineJoin();
break;
case RNSVG::BaseProp::Unknown:
default:
break;
}
}
}
}
RNSVG::SvgView RenderableView::SvgRoot() {
if (auto parent = SvgParent()) {
if (auto const &svgView{parent.try_as<RNSVG::SvgView>()}) {
if (auto const &svgViewParent = svgView.SvgParent()) {
if (auto const &renderableParent{svgViewParent.try_as<RNSVG::RenderableView>()}) {
return renderableParent.SvgRoot();
} else {
return svgView;
}
} else {
return svgView;
}
} else if (auto const &renderable{parent.try_as<RNSVG::RenderableView>()}) {
return renderable.SvgRoot();
}
}
return nullptr;
}
RNSVG::D2DGeometry RenderableView::ClipPathGeometry(RNSVG::D2DDeviceContext const &context) {
if (!m_clipPathId.empty()) {
if (auto const &clipPath{SvgRoot().Templates().TryLookup(m_clipPathId)}) {
if (!clipPath.Geometry()) {
clipPath.CreateGeometry(context);
}
return clipPath.Geometry();
}
}
return nullptr;
}
void RenderableView::Unload() {
if (m_geometry) {
m_geometry = nullptr;
}
m_parent = nullptr;
m_reactContext = nullptr;
m_propSetMap.clear();
m_strokeDashArray.Clear();
m_isUnloaded = true;
#ifndef USE_FABRIC
m_propList.clear();
#endif
}
RNSVG::IRenderable RenderableView::HitTest(Point const &point) {
if (m_geometry) {
BOOL strokeContainsPoint = FALSE;
D2D1_POINT_2F pointD2D{point.X, point.Y};
com_ptr<ID2D1Geometry> geometry{get_self<D2DGeometry>(m_geometry)->Get()};
if (auto const &svgRoot{SvgRoot()}) {
float canvasDiagonal{Utils::GetCanvasDiagonal(svgRoot.CanvasSize())};
float strokeWidth{Utils::GetAbsoluteLength(StrokeWidth(), canvasDiagonal)};
check_hresult(geometry->StrokeContainsPoint(pointD2D, strokeWidth, nullptr, nullptr, &strokeContainsPoint));
}
BOOL fillContainsPoint = FALSE;
check_hresult(geometry->FillContainsPoint(pointD2D, nullptr, &fillContainsPoint));
if (fillContainsPoint || strokeContainsPoint) {
return *this;
}
}
return nullptr;
}
#ifdef USE_FABRIC
void RenderableView::SetColor(
std::optional<ColorStruct> &color,
winrt::Microsoft::ReactNative::Color const &fallbackColor,
std::string propName) {
if (color == std::nullopt) {
propName == "fill" ? m_fill = fallbackColor : m_stroke = fallbackColor;
return;
}
switch (color->type) {
// https://github.com/software-mansion/react-native-svg/blob/main/src/lib/extract/extractBrush.ts#L29
case 1: {
propName == "fill" ? m_fillBrushId = winrt::to_hstring(color->brushRef)
: m_strokeBrushId = winrt::to_hstring(color->brushRef);
break;
}
// https://github.com/software-mansion/react-native-svg/blob/main/src/lib/extract/extractBrush.ts#L6-L8
case 2: // currentColor
case 3: // context-fill
case 4: // context-stroke
propName == "fill" ? m_fillBrushId = L"currentColor" : m_strokeBrushId = L"currentColor";
break;
default: {
auto const &c = color->payload ? color->payload : fallbackColor;
propName == "fill" ? m_fill = c : m_stroke = c;
break;
}
}
}
#else
void RenderableView::SetColor(
const JSValueObject &propValue,
Windows::UI::Color const &fallbackColor,
std::string propName) {
switch (propValue["type"].AsInt64()) {
// https://github.com/software-mansion/react-native-svg/blob/main/src/lib/extract/extractBrush.ts#L29
case 1: {
auto const &brushId{to_hstring(Utils::JSValueAsString(propValue["brushRef"]))};
propName == "fill" ? m_fillBrushId = brushId : m_strokeBrushId = brushId;
break;
}
// https://github.com/software-mansion/react-native-svg/blob/main/src/lib/extract/extractBrush.ts#L6-L8
case 2: // currentColor
case 3: // context-fill
case 4: // context-stroke
propName == "fill" ? m_fillBrushId = L"currentColor" : m_strokeBrushId = L"currentColor";
break;
default: {
auto const &color{Utils::JSValueAsColor(propValue["payload"], fallbackColor)};
propName == "fill" ? m_fill = color : m_stroke = color;
break;
}
}
}
#endif
} // namespace winrt::RNSVG::implementation