#include "pch.h" #include "SvgView.h" #include "D2DHelpers.h" #include "GroupView.h" #include #include #include #include #include namespace winrt::RNSVG::implementation { SvgViewProps::SvgViewProps(const winrt::Microsoft::ReactNative::ViewProps& props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) : m_props(props) { if (cloneFrom) { auto cloneFromProps = cloneFrom.as(); minX = cloneFromProps->minX; minY = cloneFromProps->minY; vbWidth = cloneFromProps->vbWidth; vbHeight = cloneFromProps->vbHeight; align = cloneFromProps->align; meetOrSlice = cloneFromProps->meetOrSlice; color = cloneFromProps->color; } } void SvgViewProps::SetProp( uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept { winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this); } SvgView::SvgView(const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext) : m_compContext(compContext) {} winrt::Microsoft::ReactNative::Composition::Experimental::IVisual SvgView::CreateInternalVisual() { m_visual = m_compContext.CreateSpriteVisual(); m_visual.Comment(L"SVGRoot"); return m_visual; } void SvgView::MountChildComponentView( const winrt::Microsoft::ReactNative::ComponentView &, const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &) noexcept { Invalidate(); } void SvgView::UnmountChildComponentView( const winrt::Microsoft::ReactNative::ComponentView &, const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &) noexcept { Invalidate(); } void SvgView::OnThemeChanged() noexcept { Invalidate(); } void SvgView::OnMounted() noexcept { m_isMounted = true; Invalidate(); } void SvgView::OnUnmounted() noexcept { m_isMounted = false; } D2D1_SVG_ASPECT_ALIGN AlignToAspectAlign(const std::string &align) noexcept { if (align.compare("xMinYMin") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MIN_Y_MIN; else if (align.compare("xMidYMin") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MID_Y_MIN; else if (align.compare("xMaxYMin") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MAX_Y_MIN; else if (align.compare("xMinYMid") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MIN_Y_MID; else if (align.compare("xMidYMid") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MID_Y_MID; else if (align.compare("xMaxYMid") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MAX_Y_MID; else if (align.compare("xMinYMax") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MIN_Y_MAX; else if (align.compare("xMidYMax") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MID_Y_MAX; else if (align.compare("xMaxYMax") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_X_MAX_Y_MAX; else if (align.compare("none") == 0) return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_NONE; assert(false); return D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_NONE; } void SvgView::UpdateProps( const winrt::Microsoft::ReactNative::ComponentView & /*view*/, const winrt::Microsoft::ReactNative::IComponentProps &newProps, const winrt::Microsoft::ReactNative::IComponentProps & /*oldProps*/) noexcept { m_props = newProps.as(); if (m_props->align) { m_aspectAlign = AlignToAspectAlign(m_props->align.value()); } else { m_aspectAlign = D2D1_SVG_ASPECT_ALIGN::D2D1_SVG_ASPECT_ALIGN_NONE; } } void SvgView::FinalizeUpates( const winrt::Microsoft::ReactNative::ComponentView & /*view*/, winrt::Microsoft::ReactNative::ComponentViewUpdateMask) noexcept { Invalidate(); // Move to finalize } void SvgView::Initialize(const winrt::Microsoft::ReactNative::ComponentView &sender) noexcept { auto view = sender.as(); m_wkView = view; sender.as() .CreateInternalVisualHandler([wkThis = get_weak()](const winrt::Microsoft::ReactNative::ComponentView &) { return wkThis.get()->CreateInternalVisual(); }); sender.LayoutMetricsChanged( [wkThis = get_weak()]( const winrt::Windows::Foundation::IInspectable &, const winrt::Microsoft::ReactNative::LayoutMetricsChangedArgs &args) { if (auto strongThis = wkThis.get()) { strongThis->UpdateLayoutMetrics(args.NewLayoutMetrics(), args.OldLayoutMetrics()); } }); view.ThemeChanged( [wkThis = get_weak()](const winrt::Windows::Foundation::IInspectable & /*sender*/, const winrt::Windows::Foundation::IInspectable & /*args*/) { if (auto strongThis = wkThis.get()) { strongThis->OnThemeChanged(); } }); view.Mounted([wkThis = get_weak()]( const winrt::Windows::Foundation::IInspectable & /*sender*/, const winrt::Microsoft::ReactNative::ComponentView &) { if (auto strongThis = wkThis.get()) { strongThis->OnMounted(); } }); view.Unmounted([wkThis = get_weak()]( const winrt::Windows::Foundation::IInspectable & /*sender*/, const winrt::Microsoft::ReactNative::ComponentView &) { if (auto strongThis = wkThis.get()) { strongThis->OnUnmounted(); } }); } void SvgView::RegisterComponent(const winrt::Microsoft::ReactNative::IReactPackageBuilderFabric &builder) noexcept { builder.AddViewComponent( L"RNSVGSvgView", [](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept { builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps &cloneFrom) noexcept { return winrt::make(props, cloneFrom); }); auto compBuilder = builder.as(); compBuilder.SetViewComponentViewInitializer( [](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { auto userData = winrt::make_self( view.as() .CompositionContext()); userData->Initialize(view); view.UserData(*userData); }); compBuilder.SetViewFeatures( winrt::Microsoft::ReactNative::Composition::ComponentViewFeatures::Default & ~winrt::Microsoft::ReactNative::Composition::ComponentViewFeatures::Background); builder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::IComponentProps &newProps, const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept { auto userData = winrt::get_self(view.UserData()); userData->UpdateProps(view, newProps, oldProps); }); builder.SetFinalizeUpdateHandler( [](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept { auto userData = winrt::get_self(view.UserData()); userData->FinalizeUpates(view, mask); }); builder.SetMountChildComponentViewHandler( [](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept { auto userData = winrt::get_self(view.UserData()); return userData->MountChildComponentView(view, args); }); builder.SetUnmountChildComponentViewHandler( [](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept { auto userData = winrt::get_self(view.UserData()); return userData->UnmountChildComponentView(view, args); }); }); } void SvgView::UpdateLayoutMetrics( const winrt::Microsoft::ReactNative::LayoutMetrics &metrics, const winrt::Microsoft::ReactNative::LayoutMetrics &oldMetrics) { m_layoutMetrics = metrics; if (metrics != oldMetrics) { Invalidate(); } } void RecurseRenderNode( const SvgView *root, const winrt::Microsoft::ReactNative::ComponentView &view, ID2D1SvgDocument &document, ID2D1SvgElement &svgElement) noexcept { for (auto const &child : view.Children()) { { auto renderable = child.UserData().try_as(); if (renderable && renderable->IsSupported()) { ID2D1SvgElement &newElement = renderable->Render(*root, document, svgElement); RecurseRenderNode(root, child, document, newElement); } } } } void SvgView::Draw( const winrt::Microsoft::ReactNative::Composition::ViewComponentView &view, ID2D1DeviceContext &context, winrt::Windows::Foundation::Size const &size) noexcept { com_ptr deviceContext; deviceContext.copy_from(&context); auto deviceContext5 = deviceContext.as(); winrt::com_ptr spSvgDocument; deviceContext5->CreateSvgDocument(nullptr, D2D1_SIZE_F{size.Width, size.Height}, spSvgDocument.put()); winrt::com_ptr spRoot; spSvgDocument->GetRoot(spRoot.put()); if (m_props->vbWidth != std::nullopt || m_props->vbHeight != std::nullopt) { std::wstring viewBoxStr = std::to_wstring(m_props->minX.value_or(0)) + L" " + std::to_wstring(m_props->minY.value_or(0)) + L" " + std::to_wstring(m_props->vbWidth.value_or(0)) + L" " + std::to_wstring(m_props->vbHeight.value_or(0)); spRoot->SetAttributeValue( SvgStrings::viewBoxAttributeName, D2D1_SVG_ATTRIBUTE_STRING_TYPE::D2D1_SVG_ATTRIBUTE_STRING_TYPE_SVG, viewBoxStr.c_str()); } spRoot->SetAttributeValue( SvgStrings::widthAttributeName, D2D1_SVG_ATTRIBUTE_STRING_TYPE_SVG, std::to_wstring(size.Width).c_str()); spRoot->SetAttributeValue( SvgStrings::heightAttributeName, D2D1_SVG_ATTRIBUTE_STRING_TYPE_SVG, std::to_wstring(size.Height).c_str()); if (m_props->color) { spRoot->SetAttributeValue( SvgStrings::colorAttributeName, D2DHelpers::AsD2DColor(m_props->color.AsWindowsColor(Theme()))); } if (m_props->align != std::nullopt || m_props->meetOrSlice != std::nullopt) { D2D1_SVG_PRESERVE_ASPECT_RATIO preserveAspectRatio; preserveAspectRatio.defer = false; preserveAspectRatio.align = m_aspectAlign; preserveAspectRatio.meetOrSlice = m_props->meetOrSlice.value() == MeetOrSlice::Meet ? D2D1_SVG_ASPECT_SCALING::D2D1_SVG_ASPECT_SCALING_MEET : D2D1_SVG_ASPECT_SCALING::D2D1_SVG_ASPECT_SCALING_SLICE; spRoot->SetAttributeValue(SvgStrings::preserveAspectRatioAttributeName, preserveAspectRatio); } for (auto const &child : view.Children()) { auto renderable = child.UserData().as(); if (renderable->IsSupported()) { RecurseRenderNode(this, child, *spSvgDocument, *spRoot); } } deviceContext5->DrawSvgDocument(spSvgDocument.get()); } winrt::Microsoft::ReactNative::Composition::Theme SvgView::Theme() const noexcept { if (auto view = m_wkView.get()) { return view.Theme(); } return nullptr; } void SvgView::Invalidate() { if (auto view = m_wkView.get()) { auto size = winrt::Windows::Foundation::Size{ m_layoutMetrics.Frame.Width, m_layoutMetrics.Frame.Height }; if (!m_isMounted) { return; } if (size.Height == 0 || size.Width == 0) { return; } auto drawingSurface = m_compContext.CreateDrawingSurfaceBrush( { m_layoutMetrics.Frame.Width * m_layoutMetrics.PointScaleFactor, m_layoutMetrics.Frame.Height * m_layoutMetrics.PointScaleFactor }, winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied); POINT offset; { ::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(drawingSurface, 1.0, &offset); if (auto deviceContext = autoDraw.GetRenderTarget()) { auto transform = winrt::Windows::Foundation::Numerics::make_float3x2_translation({static_cast(offset.x / m_layoutMetrics.PointScaleFactor), static_cast(offset.y / m_layoutMetrics.PointScaleFactor)}); deviceContext->SetTransform(D2DHelpers::AsD2DTransform(transform)); deviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f)); com_ptr spDeviceContext; spDeviceContext.copy_from(deviceContext); const auto dpi = m_layoutMetrics.PointScaleFactor * 96.0f; float oldDpiX, oldDpiY; deviceContext->GetDpi(&oldDpiX, &oldDpiY); deviceContext->SetDpi(dpi, dpi); Draw(view, *spDeviceContext, size); // restore dpi to old state deviceContext->SetDpi(oldDpiX, oldDpiY); } } m_visual.Brush(drawingSurface); } } } // namespace winrt::RNSVG::implementation