mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
# 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
229 lines
6.8 KiB
C++
229 lines
6.8 KiB
C++
#include "pch.h"
|
|
|
|
#include "JSValueXaml.h"
|
|
|
|
#include "GroupView.h"
|
|
#if __has_include("GroupView.g.cpp")
|
|
#include "GroupView.g.cpp"
|
|
#endif
|
|
|
|
#include "SVGLength.h"
|
|
#include "Utils.h"
|
|
|
|
using namespace winrt;
|
|
using namespace Microsoft::ReactNative;
|
|
|
|
namespace winrt::RNSVG::implementation {
|
|
|
|
void GroupView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) {
|
|
const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)};
|
|
|
|
auto const &parent{SvgParent().try_as<RNSVG::GroupView>()};
|
|
auto fontProp{RNSVG::FontProp::Unknown};
|
|
|
|
for (auto const &pair : propertyMap) {
|
|
auto const &propertyName{pair.first};
|
|
auto const &propertyValue{pair.second};
|
|
|
|
if (propertyName == "font") {
|
|
auto const &font{propertyValue.AsObject()};
|
|
|
|
// When any of the font props update, you don't get individual updates.
|
|
// Instead, you get a new JSValueObject with all font props set on the element.
|
|
// If a prop was removed, you will not get a null type - it just won't
|
|
// be part of the new prop object, so we will reset all font values.
|
|
if (forceUpdate) {
|
|
m_fontPropMap[RNSVG::FontProp::FontSize] = false;
|
|
m_fontPropMap[RNSVG::FontProp::FontFamily] = false;
|
|
m_fontPropMap[RNSVG::FontProp::FontWeight] = false;
|
|
}
|
|
|
|
for (auto const &item : m_fontPropMap) {
|
|
if (!item.second) {
|
|
switch (item.first) {
|
|
case RNSVG::FontProp::FontSize:
|
|
m_fontSize = parent ? parent.FontSize() : 12.0f;
|
|
break;
|
|
case RNSVG::FontProp::FontFamily:
|
|
m_fontFamily = parent ? parent.FontFamily() : L"Segoe UI";
|
|
break;
|
|
case RNSVG::FontProp::FontWeight:
|
|
m_fontWeight = L"auto";
|
|
break;
|
|
default:
|
|
throw hresult_error();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto const &prop : font) {
|
|
auto const &key{prop.first};
|
|
auto const &value{prop.second};
|
|
|
|
if (key == "fontSize") {
|
|
fontProp = RNSVG::FontProp::FontSize;
|
|
if (forceUpdate || !m_fontPropMap[fontProp]) {
|
|
m_fontSize = value.AsSingle();
|
|
}
|
|
} else if (key == "fontFamily") {
|
|
fontProp = RNSVG::FontProp::FontFamily;
|
|
if (forceUpdate || !m_fontPropMap[fontProp]) {
|
|
m_fontFamily = to_hstring(value.AsString());
|
|
}
|
|
} else if (key == "fontWeight") {
|
|
fontProp = RNSVG::FontProp::FontWeight;
|
|
auto fontWeight{to_hstring(value.AsString())};
|
|
if (forceUpdate) {
|
|
m_fontWeight = fontWeight;
|
|
} else if (!m_fontPropMap[fontProp]) {
|
|
m_fontWeight = L"auto";
|
|
}
|
|
}
|
|
|
|
// forceUpdate = true means the property is being set on an element
|
|
// instead of being inherited from the parent.
|
|
if (forceUpdate && (fontProp != RNSVG::FontProp::Unknown)) {
|
|
// If the propertyValue is null, that means we reset the property
|
|
m_fontPropMap[fontProp] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
__super::UpdateProperties(reader, forceUpdate, false);
|
|
|
|
for (auto const &child : Children()) {
|
|
child.as<IRenderablePaper>().UpdateProperties(reader, false, false);
|
|
}
|
|
|
|
if (invalidate && SvgParent()) {
|
|
SvgRoot().Invalidate();
|
|
}
|
|
}
|
|
|
|
void GroupView::CreateGeometry(RNSVG::D2DDeviceContext const &context) {
|
|
std::vector<ID2D1Geometry *> geometries;
|
|
for (auto const childComponent : Children()) {
|
|
auto const child = childComponent.as<IRenderable>();
|
|
if (!child.Geometry()) {
|
|
child.CreateGeometry(context);
|
|
}
|
|
|
|
if (child.Geometry()) {
|
|
com_ptr<ID2D1Geometry> geometry{get_self<D2DGeometry>(child.Geometry())->Get()};
|
|
|
|
// This takes advantage of the fact that geometry elements are alive for
|
|
// the duration of this method and so are their D2D resources.
|
|
geometries.emplace_back(geometry.get());
|
|
}
|
|
}
|
|
|
|
if (!geometries.empty()) {
|
|
com_ptr<ID2D1DeviceContext> deviceContext{get_self<D2DDeviceContext>(context)->Get()};
|
|
|
|
com_ptr<ID2D1Factory> factory;
|
|
deviceContext->GetFactory(factory.put());
|
|
|
|
com_ptr<ID2D1GeometryGroup> group;
|
|
check_hresult(factory->CreateGeometryGroup(
|
|
D2DHelpers::GetFillRule(FillRule()), &geometries[0], static_cast<uint32_t>(geometries.size()), group.put()));
|
|
|
|
Geometry(make<RNSVG::implementation::D2DGeometry>(group.as<ID2D1Geometry>()));
|
|
}
|
|
}
|
|
|
|
void GroupView::SaveDefinition() {
|
|
__super::SaveDefinition();
|
|
|
|
for (auto const &child : Children()) {
|
|
child.as<IRenderable>().SaveDefinition();
|
|
}
|
|
}
|
|
|
|
void GroupView::MergeProperties(RNSVG::IRenderable const &other) {
|
|
__super::MergeProperties(other);
|
|
|
|
for (auto const &child : Children()) {
|
|
child.as<IRenderable>().MergeProperties(*this);
|
|
}
|
|
}
|
|
|
|
void GroupView::Draw(RNSVG::D2DDeviceContext const &context, Size const &size) {
|
|
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<ID2D1Geometry> clipPathGeometry;
|
|
|
|
if (ClipPathGeometry(context)) {
|
|
clipPathGeometry = get_self<D2DGeometry>(ClipPathGeometry(context))->Get();
|
|
}
|
|
|
|
D2DHelpers::PushOpacityLayer(deviceContext.get(), clipPathGeometry.get(), m_opacity);
|
|
|
|
if (Children().Size() == 0) {
|
|
__super::Draw(context, size);
|
|
} else {
|
|
DrawGroup(context, size);
|
|
}
|
|
|
|
deviceContext->PopLayer();
|
|
|
|
deviceContext->SetTransform(transform);
|
|
}
|
|
|
|
void GroupView::DrawGroup(RNSVG::D2DDeviceContext const &context, Size const &size) {
|
|
for (auto const &child : Children()) {
|
|
child.as<IRenderable>().Draw(context, size);
|
|
}
|
|
}
|
|
|
|
void GroupView::CreateResources() {
|
|
for (auto const &child : Children()) {
|
|
child.as<IRenderable>().CreateResources();
|
|
}
|
|
}
|
|
|
|
void GroupView::Unload() {
|
|
for (auto const &child : Children()) {
|
|
child.as<IRenderable>().Unload();
|
|
}
|
|
|
|
m_fontPropMap.clear();
|
|
|
|
m_children.Clear();
|
|
|
|
__super::Unload();
|
|
}
|
|
|
|
winrt::RNSVG::IRenderable GroupView::HitTest(Point const &point) {
|
|
RNSVG::IRenderable renderable{nullptr};
|
|
if (IsResponsible()) {
|
|
for (auto const &child : Children()) {
|
|
if (auto const &hit{child.as<IRenderable>().HitTest(point)}) {
|
|
renderable = hit;
|
|
}
|
|
}
|
|
if (renderable && !renderable.IsResponsible()) {
|
|
return *this;
|
|
} else if (!renderable) {
|
|
if (Geometry()) {
|
|
com_ptr<ID2D1Geometry> geometry{get_self<D2DGeometry>(Geometry())->Get()};
|
|
|
|
D2D1_RECT_F bounds;
|
|
check_hresult(geometry->GetBounds(nullptr, &bounds));
|
|
|
|
if (xaml::RectHelper::Contains(D2DHelpers::FromD2DRect(bounds), point)) {
|
|
return *this;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return renderable;
|
|
}
|
|
} // namespace winrt::RNSVG::implementation
|