diff --git a/README.md b/README.md index a674f2fc..6f200963 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Version](https://img.shields.io/npm/v/react-native-svg.svg)](https://www.npmjs.com/package/react-native-svg) [![NPM](https://img.shields.io/npm/dm/react-native-svg.svg)](https://www.npmjs.com/package/react-native-svg) -`react-native-svg` provides SVG support to React Native on iOS and Android, and a compatibility layer for the web. +`react-native-svg` provides SVG support to React Native on iOS, Android, macOS, Windows, and a compatibility layer for the web. [Check out the demo](https://snack.expo.io/@msand/react-native-svg-example) diff --git a/package.json b/package.json index 71eec2a2..521373c9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "lib", "src", "RNSVG.podspec", - "!android/build" + "!android/build", + "windows" ], "@react-native-community/bob": { "source": "src", @@ -36,6 +37,7 @@ "react-native", "ios", "android", + "windows", "SVG", "ART", "VML", @@ -87,8 +89,9 @@ "jest": "^28.1.0", "pegjs": "^0.10.0", "prettier": "^2.6.2", - "react": "^16.13.0", - "react-native": "^0.62.3", + "react": "^17.0.1", + "react-native": "^0.64.0", + "react-native-windows": "^0.64.0", "react-test-renderer": "^16.13.0", "release-it": "^14.12.5", "ts-node": "^10.8.0", diff --git a/windows/.clang-format b/windows/.clang-format new file mode 100644 index 00000000..e3f14428 --- /dev/null +++ b/windows/.clang-format @@ -0,0 +1,91 @@ +--- +AccessModifierOffset: -1 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, TEST_CLASS, TEST_CLASS_EX ] +IncludeBlocks: Preserve +IncludeCategories: + - Regex: 'pch.h' + Priority: -1 + - Regex: '.*\.g\..*' + Priority: 1 + - Regex: '^<.*\.h(pp)?>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 + - Regex: '.*' + Priority: 4 +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never \ No newline at end of file diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..4ea0c7b5 --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,92 @@ +*AppPackages* +*BundleArtifacts* + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +#MonoDevelop +*.pidb +*.userprefs + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* +*.sass-cache + +#Project files +[Bb]uild/ + +#Subversion files +.svn + +# Office Temp Files +~$* + +# vim Temp Files +*~ + +#NuGet +packages/ +*.nupkg + +#ncrunch +*ncrunch* +*crunch*.local.xml + +# visual studio database projects +*.dbmdl + +#Test files +*.testsettings + +#Other files +*.DotSettings +.vs/ +*project.lock.json + +#Files generated by the VS build +**/Generated Files/** + diff --git a/windows/RNSVG.sln b/windows/RNSVG.sln new file mode 100644 index 00000000..3903eda5 --- /dev/null +++ b/windows/RNSVG.sln @@ -0,0 +1,187 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29215.179 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RNSVG", "RNSVG\RNSVG.vcxproj", "{7ACF84EC-EFBA-4043-8E14-40B159508902}" + ProjectSection(ProjectDependencies) = postProject + {F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {F7D32BD0-2749-483E-9A0D-1635EF7E3136} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}" + ProjectSection(ProjectDependencies) = postProject + {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|ARM.ActiveCfg = Debug|ARM + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|ARM.Build.0 = Debug|ARM + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|ARM.Deploy.0 = Debug|ARM + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|ARM64.Build.0 = Debug|ARM64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|x64.ActiveCfg = Debug|x64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|x64.Build.0 = Debug|x64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|x64.Deploy.0 = Debug|x64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|x86.ActiveCfg = Debug|Win32 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|x86.Build.0 = Debug|Win32 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Debug|x86.Deploy.0 = Debug|Win32 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|ARM.ActiveCfg = Release|ARM + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|ARM.Build.0 = Release|ARM + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|ARM.Deploy.0 = Release|ARM + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|ARM64.ActiveCfg = Release|ARM64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|ARM64.Build.0 = Release|ARM64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|ARM64.Deploy.0 = Release|ARM64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|x64.ActiveCfg = Release|x64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|x64.Build.0 = Release|x64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|x64.Deploy.0 = Release|x64 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|x86.ActiveCfg = Release|Win32 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|x86.Build.0 = Release|Win32 + {7ACF84EC-EFBA-4043-8E14-40B159508902}.Release|x86.Deploy.0 = Release|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32 + {A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {0CC28589-39E4-4288-B162-97B959F8B843} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D43FAD39-F619-437D-BB40-04A3982ACB6A} + EndGlobalSection +EndGlobal diff --git a/windows/RNSVG/BrushView.cpp b/windows/RNSVG/BrushView.cpp new file mode 100644 index 00000000..24f11dee --- /dev/null +++ b/windows/RNSVG/BrushView.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "BrushView.h" +#include "BrushView.g.cpp" + +namespace winrt::RNSVG::implementation { +void BrushView::SaveDefinition() { + if (auto const &root{SvgRoot()}) { + CreateBrush(); + root.Brushes().Insert(Id(), *this); + } +} + +void BrushView::SetBounds(Windows::Foundation::Rect const &rect) { + m_bounds = rect; + UpdateBounds(); +} + +void BrushView::Unload() { + m_brush.Close(); + m_brush = nullptr; + + __super::Unload(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/BrushView.h b/windows/RNSVG/BrushView.h new file mode 100644 index 00000000..a64b356a --- /dev/null +++ b/windows/RNSVG/BrushView.h @@ -0,0 +1,28 @@ +#pragma once +#include "BrushView.g.h" +#include "GroupView.h" + +namespace winrt::RNSVG::implementation { +struct BrushView : BrushViewT { + public: + BrushView() = default; + + void SaveDefinition(); + + Microsoft::Graphics::Canvas::Brushes::ICanvasBrush Brush() { return m_brush; } + virtual void CreateBrush() {} + virtual void Unload(); + void SetBounds(Windows::Foundation::Rect const &rect); + + + protected: + Microsoft::Graphics::Canvas::Brushes::ICanvasBrush m_brush{nullptr}; + Windows::Foundation::Rect m_bounds{}; + + virtual void UpdateBounds() {} +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct BrushView : BrushViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/CircleView.cpp b/windows/RNSVG/CircleView.cpp new file mode 100644 index 00000000..4ebe8b2a --- /dev/null +++ b/windows/RNSVG/CircleView.cpp @@ -0,0 +1,41 @@ +#include "pch.h" +#include "CircleView.h" +#include "CircleView.g.cpp" + +#include "JSValueXaml.h" +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void CircleView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "r") { + m_r = SVGLength::From(propertyValue); + } else if (propertyName == "cx") { + m_cx = SVGLength::From(propertyValue); + } else if (propertyName == "cy") { + m_cy = SVGLength::From(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void CircleView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + + float cx{Utils::GetAbsoluteLength(m_cx, canvas.Size().Width)}; + float cy{Utils::GetAbsoluteLength(m_cy, canvas.Size().Height)}; + float r{Utils::GetAbsoluteLength(m_r, Utils::GetCanvasDiagonal(canvas.Size()))}; + + Geometry(Geometry::CanvasGeometry::CreateCircle(resourceCreator, cx, cy, r)); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/CircleView.h b/windows/RNSVG/CircleView.h new file mode 100644 index 00000000..6b1a278a --- /dev/null +++ b/windows/RNSVG/CircleView.h @@ -0,0 +1,20 @@ +#pragma once +#include "CircleView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct CircleView : CircleViewT { + public: + CircleView() = default; + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + + private: + RNSVG::SVGLength m_r{}; + RNSVG::SVGLength m_cx{}; + RNSVG::SVGLength m_cy{}; +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct CircleView : CircleViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/CircleViewManager.cpp b/windows/RNSVG/CircleViewManager.cpp new file mode 100644 index 00000000..0016c0a6 --- /dev/null +++ b/windows/RNSVG/CircleViewManager.cpp @@ -0,0 +1,28 @@ +#include "pch.h" +#include "CircleViewManager.h" +#include "CircleViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +CircleViewManager::CircleViewManager() { + m_class = RNSVG::SVGClass::RNSVGCircle; + m_name = L"RNSVGCircle"; +} + +IMapView CircleViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"cx", ViewManagerPropertyType::String); + nativeProps.Insert(L"cy", ViewManagerPropertyType::String); + nativeProps.Insert(L"r", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/CircleViewManager.h b/windows/RNSVG/CircleViewManager.h new file mode 100644 index 00000000..dda83aa5 --- /dev/null +++ b/windows/RNSVG/CircleViewManager.h @@ -0,0 +1,15 @@ +#pragma once +#include "CircleViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct CircleViewManager : CircleViewManagerT { + CircleViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct CircleViewManager : CircleViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/DefsView.cpp b/windows/RNSVG/DefsView.cpp new file mode 100644 index 00000000..018f6f29 --- /dev/null +++ b/windows/RNSVG/DefsView.cpp @@ -0,0 +1,10 @@ +#include "pch.h" +#include "DefsView.h" +#include "DefsView.g.cpp" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; + +namespace winrt::RNSVG::implementation { +void DefsView::Render(UI::Xaml::CanvasControl const &/*canvas*/, CanvasDrawingSession const &/*session*/) {} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/DefsView.h b/windows/RNSVG/DefsView.h new file mode 100644 index 00000000..6f2c2f01 --- /dev/null +++ b/windows/RNSVG/DefsView.h @@ -0,0 +1,17 @@ +#pragma once +#include "DefsView.g.h" +#include "GroupView.h" + +namespace winrt::RNSVG::implementation { +struct DefsView : DefsViewT { + DefsView() = default; + + void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct DefsView : DefsViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/DefsViewManager.cpp b/windows/RNSVG/DefsViewManager.cpp new file mode 100644 index 00000000..f08fb42c --- /dev/null +++ b/windows/RNSVG/DefsViewManager.cpp @@ -0,0 +1,10 @@ +#include "pch.h" +#include "DefsViewManager.h" +#include "DefsViewManager.g.cpp" + +namespace winrt::RNSVG::implementation { +DefsViewManager::DefsViewManager() { + m_class = RNSVG::SVGClass::RNSVGDefs; + m_name = L"RNSVGDefs"; +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/DefsViewManager.h b/windows/RNSVG/DefsViewManager.h new file mode 100644 index 00000000..1555db56 --- /dev/null +++ b/windows/RNSVG/DefsViewManager.h @@ -0,0 +1,12 @@ +#pragma once +#include "DefsViewManager.g.h" +#include "GroupViewManager.h" + +namespace winrt::RNSVG::implementation { +struct DefsViewManager : DefsViewManagerT { + DefsViewManager(); +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct DefsViewManager : DefsViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/EllipseView.cpp b/windows/RNSVG/EllipseView.cpp new file mode 100644 index 00000000..54693d4f --- /dev/null +++ b/windows/RNSVG/EllipseView.cpp @@ -0,0 +1,44 @@ +#include "pch.h" +#include "EllipseView.h" +#include "EllipseView.g.cpp" + +#include "JSValueXaml.h" +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void EllipseView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "cx") { + m_cx = SVGLength::From(propertyValue); + } else if (propertyName == "cy") { + m_cy = SVGLength::From(propertyValue); + } else if (propertyName == "rx") { + m_rx = SVGLength::From(propertyValue); + } else if (propertyName == "ry") { + m_ry = SVGLength::From(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void EllipseView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + + float cx{Utils::GetAbsoluteLength(m_cx, canvas.Size().Width)}; + float cy{Utils::GetAbsoluteLength(m_cy, canvas.Size().Height)}; + float rx{Utils::GetAbsoluteLength(m_rx, canvas.Size().Width)}; + float ry{Utils::GetAbsoluteLength(m_ry, canvas.Size().Height)}; + + Geometry(Geometry::CanvasGeometry::CreateEllipse(resourceCreator, cx, cy, rx, ry)); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/EllipseView.h b/windows/RNSVG/EllipseView.h new file mode 100644 index 00000000..7a58ac4f --- /dev/null +++ b/windows/RNSVG/EllipseView.h @@ -0,0 +1,21 @@ +#pragma once +#include "EllipseView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct EllipseView : EllipseViewT { + public: + EllipseView() = default; + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + + private: + RNSVG::SVGLength m_cx{}; + RNSVG::SVGLength m_cy{}; + RNSVG::SVGLength m_rx{}; + RNSVG::SVGLength m_ry{}; +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct EllipseView : EllipseViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/EllipseViewManager.cpp b/windows/RNSVG/EllipseViewManager.cpp new file mode 100644 index 00000000..57397213 --- /dev/null +++ b/windows/RNSVG/EllipseViewManager.cpp @@ -0,0 +1,29 @@ +#include "pch.h" +#include "EllipseViewManager.h" +#include "EllipseViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +EllipseViewManager::EllipseViewManager() { + m_class = RNSVG::SVGClass::RNSVGEllipse; + m_name = L"RNSVGEllipse"; +} + +IMapView EllipseViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"cx", ViewManagerPropertyType::String); + nativeProps.Insert(L"cy", ViewManagerPropertyType::String); + nativeProps.Insert(L"rx", ViewManagerPropertyType::String); + nativeProps.Insert(L"ry", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/EllipseViewManager.h b/windows/RNSVG/EllipseViewManager.h new file mode 100644 index 00000000..18d6bfe2 --- /dev/null +++ b/windows/RNSVG/EllipseViewManager.h @@ -0,0 +1,15 @@ +#pragma once +#include "EllipseViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct EllipseViewManager : EllipseViewManagerT { + EllipseViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct EllipseViewManager : EllipseViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/GroupView.cpp b/windows/RNSVG/GroupView.cpp new file mode 100644 index 00000000..8e103239 --- /dev/null +++ b/windows/RNSVG/GroupView.cpp @@ -0,0 +1,172 @@ +#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::Graphics::Canvas; +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()}; + 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.UpdateProperties(reader, false, false); + } + + if (invalidate && SvgParent()) { + SvgRoot().InvalidateCanvas(); + } +} + +void GroupView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + std::vector geometries; + for (auto const &child : Children()) { + geometries.push_back(child.Geometry()); + } + + Geometry(Geometry::CanvasGeometry::CreateGroup(resourceCreator, geometries, FillRule())); +} + +void GroupView::SaveDefinition() { + __super::SaveDefinition(); + + for (auto const &child : Children()) { + child.SaveDefinition(); + } +} + +void GroupView::MergeProperties(RNSVG::RenderableView const &other) { + __super::MergeProperties(other); + + for (auto const &child : Children()) { + child.MergeProperties(*this); + } +} + +void GroupView::Render(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + auto const &transform{session.Transform()}; + + if (m_propSetMap[RNSVG::BaseProp::Matrix]) { + session.Transform(transform * SvgTransform()); + } + + if (auto const &opacityLayer{session.CreateLayer(m_opacity)}) { + if (Children().Size() == 0) { + __super::Render(canvas, session); + } else { + RenderGroup(canvas, session); + } + + opacityLayer.Close(); + } + session.Transform(transform); +} + +void GroupView::RenderGroup(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + for (auto const &child : Children()) { + child.Render(canvas, session); + } +} + +void GroupView::CreateResources(ICanvasResourceCreator const &resourceCreator, UI::CanvasCreateResourcesEventArgs const &args) { + for (auto const &child : Children()) { + child.CreateResources(resourceCreator, args); + } +} + +void GroupView::Unload() { + for (auto const &child : Children()) { + child.Unload(); + } + + m_reactContext = nullptr; + m_fontPropMap.clear(); + m_children.Clear(); + + __super::Unload(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/GroupView.h b/windows/RNSVG/GroupView.h new file mode 100644 index 00000000..3493bafb --- /dev/null +++ b/windows/RNSVG/GroupView.h @@ -0,0 +1,62 @@ +#pragma once +#include "GroupView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct GroupView : GroupViewT { + public: + GroupView() = default; + GroupView(Microsoft::ReactNative::IReactContext const &context) : m_reactContext(context) {} + + Windows::Foundation::Collections::IVector Children() { return m_children; } + + hstring FontFamily() { return m_fontFamily; } + void FontFamily(hstring const &value) { m_fontFamily = value; } + + float FontSize() { return m_fontSize; } + void FontSize(float value) { m_fontSize = value; } + + hstring FontWeight(){ return m_fontWeight; } + void FontWeight(hstring const &value) { m_fontWeight = value; } + + virtual void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + virtual void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + + virtual void SaveDefinition(); + + virtual void MergeProperties(RNSVG::RenderableView const &other); + + virtual void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + + virtual void RenderGroup( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + + virtual void CreateResources( + Microsoft::Graphics::Canvas::ICanvasResourceCreator const &resourceCreator, + Microsoft::Graphics::Canvas::UI::CanvasCreateResourcesEventArgs const &args); + + virtual void Unload(); + + private: + Microsoft::ReactNative::IReactContext m_reactContext{nullptr}; + Windows::Foundation::Collections::IVector m_children{ + winrt::single_threaded_vector()}; + + float m_fontSize{12.0f}; + hstring m_fontFamily{L"Segoe UI"}; + hstring m_fontWeight{L"auto"}; + + std::map m_fontPropMap{ + {RNSVG::FontProp::FontSize, false}, + {RNSVG::FontProp::FontWeight, false}, + {RNSVG::FontProp::FontFamily, false}, + }; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct GroupView : GroupViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/GroupViewManager.cpp b/windows/RNSVG/GroupViewManager.cpp new file mode 100644 index 00000000..1f0d349e --- /dev/null +++ b/windows/RNSVG/GroupViewManager.cpp @@ -0,0 +1,107 @@ +#include "pch.h" +#include "GroupViewManager.h" +#if __has_include("GroupViewManager.g.cpp") +#include "GroupViewManager.g.cpp" +#endif + +#include "GroupView.h" + +#include + +using namespace winrt; +using namespace Microsoft::ReactNative; + +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::UI::Xaml; + +namespace winrt::RNSVG::implementation { +GroupViewManager::GroupViewManager() { + m_class = RNSVG::SVGClass::RNSVGGroup; + m_name = L"RNSVGGroup"; +} + +// IViewManagerWithNativeProperties +IMapView GroupViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"font", ViewManagerPropertyType::Map); + + return nativeProps.GetView(); +} + +// IViewManagerWithChildren +void GroupViewManager::AddView(FrameworkElement const &parent, UIElement const &child, int64_t /*index*/) { + if (auto const &groupView{parent.try_as()}) { + if (auto const &childView{child.try_as()}) { + childView.SvgParent(parent); + groupView.Children().Append(childView); + childView.MergeProperties(groupView); + + if (auto const &root{groupView.SvgRoot()}) { + root.InvalidateCanvas(); + } + } + } +} + +void GroupViewManager::RemoveAllChildren(FrameworkElement const &parent) { + if (auto const &groupView{parent.try_as()}) { + for (auto const &child : groupView.Children()) { + child.Unload(); + child.SvgParent(nullptr); + } + + groupView.Children().Clear(); + + if (auto const &root{groupView.SvgRoot()}) { + root.InvalidateCanvas(); + } + } +} + +void GroupViewManager::RemoveChildAt(FrameworkElement const &parent, int64_t index) { + if (auto const &groupView{parent.try_as()}) { + auto const &child{groupView.Children().GetAt(static_cast(index))}; + child.Unload(); + child.SvgParent(nullptr); + + groupView.Children().RemoveAt(static_cast(index)); + + if (auto const &root{groupView.SvgRoot()}) { + root.InvalidateCanvas(); + } + } +} + +void GroupViewManager::ReplaceChild( + FrameworkElement const &parent, + UIElement const &oldChild, + UIElement const &newChild) { + auto const &groupView{parent.try_as()}; + auto const &oldChildView{oldChild.try_as()}; + auto const &newChildView{newChild.try_as()}; + + if (groupView && oldChildView && newChildView) { + uint32_t index; + + if (groupView.Children().IndexOf(oldChildView, index)) { + groupView.Children().RemoveAt(index); + oldChildView.Unload(); + oldChildView.SvgParent(nullptr); + newChildView.SvgParent(parent); + groupView.Children().Append(newChildView); + newChildView.MergeProperties(groupView); + + if (auto const &root{groupView.SvgRoot()}) { + root.InvalidateCanvas(); + } + } + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/GroupViewManager.h b/windows/RNSVG/GroupViewManager.h new file mode 100644 index 00000000..ad27d020 --- /dev/null +++ b/windows/RNSVG/GroupViewManager.h @@ -0,0 +1,28 @@ +#pragma once + +#include "GroupViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct GroupViewManager : GroupViewManagerT { + public: + GroupViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); + + // IViewManagerWithChildren + void + AddView(Windows::UI::Xaml::FrameworkElement const &parent, Windows::UI::Xaml::UIElement const &child, int64_t index); + void RemoveAllChildren(Windows::UI::Xaml::FrameworkElement const &parent); + void RemoveChildAt(Windows::UI::Xaml::FrameworkElement const &parent, int64_t index); + void ReplaceChild( + Windows::UI::Xaml::FrameworkElement const &parent, + Windows::UI::Xaml::UIElement const &oldChild, + Windows::UI::Xaml::UIElement const &newChild); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct GroupViewManager : GroupViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/ImageView.cpp b/windows/RNSVG/ImageView.cpp new file mode 100644 index 00000000..cba576fb --- /dev/null +++ b/windows/RNSVG/ImageView.cpp @@ -0,0 +1,267 @@ +#include "pch.h" +#include "ImageView.h" +#include "ImageView.g.cpp" + +#include +#include +#include +#include +#include + +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; +using namespace Windows::Security::Cryptography; +using namespace Windows::Storage::Streams; +using namespace Windows::Web::Http; + +namespace winrt::RNSVG::implementation { +void ImageView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "src") { + auto const &src{propertyValue.AsObject()}; + + for (auto const &item : src) { + auto const &key{item.first}; + auto const &value{item.second}; + + if (key == "uri") { + m_source.uri = to_hstring(Utils::JSValueAsString(value)); + m_source.type = ImageSourceType::Uri; + m_source.format = ImageSourceFormat::Bitmap; + m_source.width = 0; + m_source.height = 0; + + if (SvgParent()) { + LoadImageSourceAsync(SvgRoot().Canvas(), true); + } + } else if (key == "width") { + m_source.width = Utils::JSValueAsFloat(value); + } else if (key == "height") { + m_source.height = Utils::JSValueAsFloat(value); + } else if (key == "scale") { + m_source.scale = Utils::JSValueAsFloat(value); + } else if (key == "method") { + m_source.method = to_hstring(Utils::JSValueAsString(value)); + } else if (key == "headers") { + m_source.headers.clear(); + for (auto const &header : value.AsObject()) { + m_source.headers.push_back(std::make_pair(to_hstring(header.first), to_hstring(Utils::JSValueAsString(header.second)))); + } + } else if (key == "__packager_asset") { + m_source.packagerAsset = value.AsBoolean(); + } + } + } else if (propertyName == "x") { + m_x = SVGLength::From(propertyValue); + } else if (propertyName == "y") { + m_y = SVGLength::From(propertyValue); + } else if (propertyName == "width") { + m_width = SVGLength::From(propertyValue); + } else if (propertyName == "height") { + m_height = SVGLength::From(propertyValue); + } else if (propertyName == "align") { + m_align = Utils::JSValueAsString(propertyValue); + } else if (propertyName == "meetOrSlice") { + m_meetOrSlice = Utils::GetMeetOrSlice(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void ImageView::Render(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + if (m_source.width == 0 || m_source.height == 0) { + m_source.width = canvas.Size().Width; + m_source.height = canvas.Size().Height; + } + + float x{Utils::GetAbsoluteLength(m_x, canvas.Size().Width)}; + float y{Utils::GetAbsoluteLength(m_y, canvas.Size().Height)}; + float width{Utils::GetAbsoluteLength(m_width, canvas.Size().Width)}; + float height{Utils::GetAbsoluteLength(m_height, canvas.Size().Height)}; + + if (width == 0) { + width = m_source.width * m_source.scale; + } + + if (height == 0) { + height = m_source.height * m_source.scale; + } + + Effects::Transform2DEffect transformEffect{nullptr}; + if (m_align != "") { + Rect elRect{x, y, width, height}; + Rect vbRect{0, 0, m_source.width, m_source.height}; + transformEffect = Effects::Transform2DEffect{}; + transformEffect.TransformMatrix(Utils::GetViewBoxTransform(vbRect, elRect, m_align, m_meetOrSlice)); + } + + if (auto const &opacityLayer{session.CreateLayer(m_opacity)}) { + if (m_source.format == ImageSourceFormat::Bitmap && m_bitmap) { + auto const &transform{session.Transform()}; + if (m_propSetMap[RNSVG::BaseProp::Matrix]) { + session.Transform(SvgTransform()); + } + + if (m_align != "" && transformEffect) { + transformEffect.Source(m_bitmap); + Effects::CropEffect cropEffect{}; + cropEffect.SourceRectangle({x, y, width, height}); + cropEffect.Source(transformEffect); + session.DrawImage(cropEffect); + } else { + session.DrawImage(m_bitmap, {x, y, width, height}); + } + + session.Transform(transform); + } + + opacityLayer.Close(); + } +} + +void ImageView::CreateResources(ICanvasResourceCreator const &resourceCreator, UI::CanvasCreateResourcesEventArgs const &args) { + args.TrackAsyncAction(LoadImageSourceAsync(resourceCreator, false)); +} + +void ImageView::Unload() { + if (m_bitmap) { + m_bitmap.Close(); + m_bitmap = nullptr; + } +} + +IAsyncAction ImageView::LoadImageSourceAsync(ICanvasResourceCreator resourceCreator, bool invalidate) { + Uri uri{m_source.uri}; + hstring scheme{uri ? uri.SchemeName() : L""}; + hstring ext{uri ? uri.Extension() : L""}; + + if (ext == L".svg" || ext == L".svgz") { + m_source.format = ImageSourceFormat::Svg; + co_return; + } + + if (scheme == L"http" || scheme == L"https") { + m_source.type = ImageSourceType::Download; + } else if (scheme == L"data") { + m_source.type = ImageSourceType::InlineData; + if (to_string(m_source.uri).find("image/svg+xml") != std::string::npos) { + m_source.format = ImageSourceFormat::Svg; + co_return; + } + } + + const bool fromStream{m_source.type == ImageSourceType::Download || m_source.type == ImageSourceType::InlineData}; + + InMemoryRandomAccessStream stream{nullptr}; + + // get weak reference before any co_await calls + auto weak_this{get_weak()}; + + try { + stream = co_await GetImageMemoryStreamAsync(m_source); + + if (fromStream && !stream) { + co_return; + } + } catch (winrt::hresult_error const &) { + co_return; + } + + if (stream) { + m_bitmap = co_await CanvasBitmap::LoadAsync(resourceCreator, stream); + } else { + m_bitmap = co_await CanvasBitmap::LoadAsync(resourceCreator, uri); + } + + m_source.width = m_bitmap.Size().Width; + m_source.height = m_bitmap.Size().Height; + + if (invalidate) { + if (auto strong_this{weak_this.get()}) { + strong_this->SvgRoot().InvalidateCanvas(); + } + } +} + +IAsyncOperation ImageView::GetImageMemoryStreamAsync(ImageSource source) { + switch (source.type) { + case ImageSourceType::Download: + co_return co_await GetImageStreamAsync(source); + case ImageSourceType::InlineData: + co_return co_await GetImageInlineDataAsync(source); + default: // ImageSourceType::Uri + co_return nullptr; + } +} + +IAsyncOperation ImageView::GetImageStreamAsync(ImageSource source) { + try { + co_await resume_background(); + + auto httpMethod{source.method.empty() ? HttpMethod::Get() : HttpMethod{source.method}}; + + Uri uri{source.uri}; + HttpRequestMessage request{httpMethod, uri}; + + if (!source.headers.empty()) { + for (auto const &header : source.headers) { + if (_stricmp(to_string(header.first).c_str(), "authorization") == 0) { + request.Headers().TryAppendWithoutValidation(header.first, header.second); + } else { + request.Headers().Append(header.first, header.second); + } + } + } + + HttpClient httpClient; + HttpResponseMessage response{co_await httpClient.SendRequestAsync(request)}; + + if (response && response.StatusCode() == HttpStatusCode::Ok) { + IInputStream inputStream{co_await response.Content().ReadAsInputStreamAsync()}; + InMemoryRandomAccessStream memoryStream; + co_await RandomAccessStream::CopyAsync(inputStream, memoryStream); + memoryStream.Seek(0); + + co_return memoryStream; + } + } catch (hresult_error const &) { + } + + co_return nullptr; +} + +IAsyncOperation ImageView::GetImageInlineDataAsync(ImageSource source) { + std::string uri{to_string(source.uri)}; + + size_t start = uri.find(','); + if (start == std::string::npos || start + 1 > uri.length()) { + co_return nullptr; + } + + try { + co_await winrt::resume_background(); + + std::string_view base64String{uri.c_str() + start + 1, uri.length() - start - 1}; + auto const &buffer{CryptographicBuffer::DecodeFromBase64String(to_hstring(base64String))}; + + InMemoryRandomAccessStream memoryStream; + co_await memoryStream.WriteAsync(buffer); + memoryStream.Seek(0); + + co_return memoryStream; + } catch (winrt::hresult_error const &) { + // Base64 decode failed + } + + co_return nullptr; +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/ImageView.h b/windows/RNSVG/ImageView.h new file mode 100644 index 00000000..4b9cf947 --- /dev/null +++ b/windows/RNSVG/ImageView.h @@ -0,0 +1,58 @@ +#pragma once +#include "ImageView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +enum class ImageSourceType { Uri = 0, Download = 1, InlineData = 2 }; +enum class ImageSourceFormat { Bitmap = 0, Svg = 1 }; +struct ImageSource { + hstring uri{L""}; + hstring method{L""}; + std::vector> headers{}; + float width{0.0f}; + float height{0.0f}; + float scale{1.0f}; + bool packagerAsset{false}; + ImageSourceType type{ImageSourceType::Uri}; + ImageSourceFormat format{ImageSourceFormat::Bitmap}; +}; + +struct ImageView : ImageViewT { + public: + ImageView() = default; + + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + void CreateResources( + Microsoft::Graphics::Canvas::ICanvasResourceCreator const &resourceCreator, + Microsoft::Graphics::Canvas::UI::CanvasCreateResourcesEventArgs const &args); + void Unload(); + + private: + RNSVG::SVGLength m_x{}; + RNSVG::SVGLength m_y{}; + RNSVG::SVGLength m_width{}; + RNSVG::SVGLength m_height{}; + + // preserveAspectRatio + std::string m_align{""}; + RNSVG::MeetOrSlice m_meetOrSlice{RNSVG::MeetOrSlice::Meet}; + + ImageSource m_source{}; + Microsoft::Graphics::Canvas::CanvasBitmap m_bitmap{nullptr}; + + Windows::Foundation::IAsyncAction LoadImageSourceAsync(Microsoft::Graphics::Canvas::ICanvasResourceCreator resourceCreator, bool invalidate); + winrt::Windows::Foundation::IAsyncOperation + GetImageMemoryStreamAsync(ImageSource source); + Windows::Foundation::IAsyncOperation + GetImageStreamAsync(ImageSource source); + Windows::Foundation::IAsyncOperation + GetImageInlineDataAsync(ImageSource source); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct ImageView : ImageViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/ImageViewManager.cpp b/windows/RNSVG/ImageViewManager.cpp new file mode 100644 index 00000000..a515e8b9 --- /dev/null +++ b/windows/RNSVG/ImageViewManager.cpp @@ -0,0 +1,34 @@ +#include "pch.h" +#include "ImageViewManager.h" +#include "ImageViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +ImageViewManager::ImageViewManager() { + m_class = RNSVG::SVGClass::RNSVGImage; + m_name = L"RNSVGImage"; +} + +IMapView ImageViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"x", ViewManagerPropertyType::String); + nativeProps.Insert(L"y", ViewManagerPropertyType::String); + nativeProps.Insert(L"height", ViewManagerPropertyType::String); + nativeProps.Insert(L"width", ViewManagerPropertyType::String); + nativeProps.Insert(L"src", ViewManagerPropertyType::String); + + // preserveAspectRatio + nativeProps.Insert(L"align", ViewManagerPropertyType::String); + nativeProps.Insert(L"meetOrSlice", ViewManagerPropertyType::Number); + + return nativeProps.GetView(); +} +} diff --git a/windows/RNSVG/ImageViewManager.h b/windows/RNSVG/ImageViewManager.h new file mode 100644 index 00000000..025cdcaf --- /dev/null +++ b/windows/RNSVG/ImageViewManager.h @@ -0,0 +1,16 @@ +#pragma once +#include "ImageViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct ImageViewManager : ImageViewManagerT { + ImageViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct ImageViewManager : ImageViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/LineView.cpp b/windows/RNSVG/LineView.cpp new file mode 100644 index 00000000..d1c5fd94 --- /dev/null +++ b/windows/RNSVG/LineView.cpp @@ -0,0 +1,47 @@ +#include "pch.h" +#include "LineView.h" +#include "LineView.g.cpp" + +#include "JSValueXaml.h" +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void LineView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "x1") { + m_x1 = SVGLength::From(propertyValue); + } else if (propertyName == "y1") { + m_y1 = SVGLength::From(propertyValue); + } else if (propertyName == "x2") { + m_x2 = SVGLength::From(propertyValue); + } else if (propertyName == "y2") { + m_y2 = SVGLength::From(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} +void LineView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + + float x1{Utils::GetAbsoluteLength(m_x1, canvas.Size().Width)}; + float y1{Utils::GetAbsoluteLength(m_y1, canvas.Size().Height)}; + float x2{Utils::GetAbsoluteLength(m_x2, canvas.Size().Width)}; + float y2{Utils::GetAbsoluteLength(m_y2, canvas.Size().Height)}; + + auto const &pathBuilder{Geometry::CanvasPathBuilder(resourceCreator)}; + pathBuilder.BeginFigure(x1, y1); + pathBuilder.AddLine (x2, y2); + pathBuilder.EndFigure(Geometry::CanvasFigureLoop::Open); + Geometry(Geometry::CanvasGeometry::CreatePath(pathBuilder)); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/LineView.h b/windows/RNSVG/LineView.h new file mode 100644 index 00000000..5c086460 --- /dev/null +++ b/windows/RNSVG/LineView.h @@ -0,0 +1,21 @@ +#pragma once +#include "LineView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct LineView : LineViewT { + public: + LineView() = default; + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + + private: + RNSVG::SVGLength m_x1{}; + RNSVG::SVGLength m_y1{}; + RNSVG::SVGLength m_x2{}; + RNSVG::SVGLength m_y2{}; +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct LineView : LineViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/LineViewManager.cpp b/windows/RNSVG/LineViewManager.cpp new file mode 100644 index 00000000..517987f8 --- /dev/null +++ b/windows/RNSVG/LineViewManager.cpp @@ -0,0 +1,29 @@ +#include "pch.h" +#include "LineViewManager.h" +#include "LineViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +LineViewManager::LineViewManager() { + m_class = RNSVG::SVGClass::RNSVGLine; + m_name = L"RNSVGLine"; +} + +IMapView LineViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"x1", ViewManagerPropertyType::String); + nativeProps.Insert(L"y1", ViewManagerPropertyType::String); + nativeProps.Insert(L"x2", ViewManagerPropertyType::String); + nativeProps.Insert(L"y2", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/LineViewManager.h b/windows/RNSVG/LineViewManager.h new file mode 100644 index 00000000..5859f67c --- /dev/null +++ b/windows/RNSVG/LineViewManager.h @@ -0,0 +1,15 @@ +#pragma once +#include "LineViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct LineViewManager : LineViewManagerT { + LineViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct LineViewManager : LineViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/LinearGradientView.cpp b/windows/RNSVG/LinearGradientView.cpp new file mode 100644 index 00000000..ec038221 --- /dev/null +++ b/windows/RNSVG/LinearGradientView.cpp @@ -0,0 +1,81 @@ +#include "pch.h" +#include "LinearGradientView.h" +#include "LinearGradientView.g.cpp" + +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void LinearGradientView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "x1") { + m_x1 = SVGLength::From(propertyValue); + } else if (propertyName == "y1") { + m_y1 = SVGLength::From(propertyValue); + } else if (propertyName == "x2") { + m_x2 = SVGLength::From(propertyValue); + } else if (propertyName == "y2") { + m_y2 = SVGLength::From(propertyValue); + } else if (propertyName == "gradient") { + m_stops = Utils::JSValueAsStops(propertyValue); + } else if (propertyName == "gradientUnits") { + m_gradientUnits = Utils::JSValueAsBrushUnits(propertyValue); + } else if (propertyName == "gradientTransform") { + m_transformSet = true; + m_transform = Utils::JSValueAsTransform(propertyValue); + + if (propertyValue.IsNull()) { + m_transformSet = false; + } + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); + + SaveDefinition(); +} + +void LinearGradientView::Unload() { + m_stops.clear(); + __super::Unload(); +} + +void LinearGradientView::CreateBrush() { + auto const &canvas{SvgRoot().Canvas()}; + auto const &resourceCreator{canvas.try_as()}; + Brushes::CanvasLinearGradientBrush brush{resourceCreator, m_stops}; + + SetPoints(brush, {0, 0, canvas.Size().Width, canvas.Size().Height}); + + if (m_transformSet) { + brush.Transform(m_transform); + } + + m_brush = brush; +} + +void LinearGradientView::UpdateBounds() { + if (m_gradientUnits == "objectBoundingBox") { + SetPoints(m_brush.as(), m_bounds); + } +} + +void LinearGradientView::SetPoints(Brushes::CanvasLinearGradientBrush brush, Windows::Foundation::Rect const &bounds) { + float x1{Utils::GetAbsoluteLength(m_x1, bounds.Width) + bounds.X}; + float y1{Utils::GetAbsoluteLength(m_y1, bounds.Height) + bounds.Y}; + float x2{Utils::GetAbsoluteLength(m_x2, bounds.Width) + bounds.X}; + float y2{Utils::GetAbsoluteLength(m_y2, bounds.Height) + bounds.Y}; + + brush.StartPoint({x1, y1}); + brush.EndPoint({x2, y2}); +} + +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/LinearGradientView.h b/windows/RNSVG/LinearGradientView.h new file mode 100644 index 00000000..071edad2 --- /dev/null +++ b/windows/RNSVG/LinearGradientView.h @@ -0,0 +1,32 @@ +#pragma once +#include "LinearGradientView.g.h" +#include "BrushView.h" + +namespace winrt::RNSVG::implementation { +struct LinearGradientView : LinearGradientViewT { + public: + LinearGradientView() = default; + + // RenderableView + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void Unload(); + + private: + RNSVG::SVGLength m_x1{}; + RNSVG::SVGLength m_y1{}; + RNSVG::SVGLength m_x2{}; + RNSVG::SVGLength m_y2{}; + std::vector m_stops{}; + std::string m_gradientUnits{"objectBoundingBox"}; + bool m_transformSet{false}; + Numerics::float3x2 m_transform{Numerics::make_float3x2_scale(1)}; + + void CreateBrush(); + void UpdateBounds(); + void SetPoints(Microsoft::Graphics::Canvas::Brushes::CanvasLinearGradientBrush brush, Windows::Foundation::Rect const &bounds); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct LinearGradientView : LinearGradientViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/LinearGradientViewManager.cpp b/windows/RNSVG/LinearGradientViewManager.cpp new file mode 100644 index 00000000..ba150ce1 --- /dev/null +++ b/windows/RNSVG/LinearGradientViewManager.cpp @@ -0,0 +1,32 @@ +#include "pch.h" +#include "LinearGradientViewManager.h" +#include "LinearGradientViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +LinearGradientViewManager::LinearGradientViewManager() { + m_class = RNSVG::SVGClass::RNSVGLinearGradient; + m_name = L"RNSVGLinearGradient"; +} + +IMapView LinearGradientViewManager::NativeProps() { + auto const& parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"x1", ViewManagerPropertyType::String); + nativeProps.Insert(L"y1", ViewManagerPropertyType::String); + nativeProps.Insert(L"x2", ViewManagerPropertyType::String); + nativeProps.Insert(L"y2", ViewManagerPropertyType::String); + nativeProps.Insert(L"gradient", ViewManagerPropertyType::Array); + nativeProps.Insert(L"gradientUnits", ViewManagerPropertyType::Number); + nativeProps.Insert(L"gradientTransform", ViewManagerPropertyType::Array); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/LinearGradientViewManager.h b/windows/RNSVG/LinearGradientViewManager.h new file mode 100644 index 00000000..995c3894 --- /dev/null +++ b/windows/RNSVG/LinearGradientViewManager.h @@ -0,0 +1,18 @@ +#pragma once +#include "LinearGradientViewManager.g.h" +#include "GroupViewManager.h" + +namespace winrt::RNSVG::implementation { +struct LinearGradientViewManager + : LinearGradientViewManagerT { + LinearGradientViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct LinearGradientViewManager + : LinearGradientViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/PathView.cpp b/windows/RNSVG/PathView.cpp new file mode 100644 index 00000000..cceb048e --- /dev/null +++ b/windows/RNSVG/PathView.cpp @@ -0,0 +1,295 @@ +#include "pch.h" +#include "PathView.h" +#if __has_include("PathView.g.cpp") +#include "PathView.g.cpp" +#endif + +#include + +#include "JSValueXaml.h" +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void PathView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "d") { + m_commands.clear(); + m_segmentData.clear(); + + if (propertyValue.IsNull()) { + m_d.clear(); + } else { + m_d = propertyValue.AsString(); + ParsePath(); + } + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void PathView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + Svg::CanvasSvgDocument doc{resourceCreator}; + auto const &path{doc.CreatePathAttribute(m_segmentData, m_commands)}; + Geometry(path.CreatePathGeometry()); +} + +void PathView::ParsePath() { + char prev_cmd = ' '; + + size_t i{0}; + auto length{m_d.length()}; + while (i < length) { + SkipSpaces(i); + + if (i > length) { + break; + } + + bool has_prev_cmd{prev_cmd != ' '}; + char first_char = m_d.at(i); + + if (!has_prev_cmd && first_char != 'M' && first_char != 'm') { + throw hresult_invalid_argument(L"First segment must be a MoveTo."); + } + + bool is_implicit_move_to{false}; + char cmd = ' '; + + if (IsCommand(first_char)) { + cmd = first_char; + m_commands.push_back(m_cmds[cmd]); + ++i; + } else if (has_prev_cmd && IsNumberStart(first_char)) { + if (prev_cmd == 'Z' || prev_cmd == 'z') { + throw hresult_invalid_argument(L"ClosePath cannot be followed by a number."); + } + + if (prev_cmd == 'M' || prev_cmd == 'm') { + // If a MoveTo is followed by multiple pairs of coordinates, + // the subsequent pairs are treated as implicit LineTo commands. + is_implicit_move_to = true; + if (IsUpper(prev_cmd)) { + cmd = 'L'; + m_commands.push_back(m_cmds[cmd]); + } else { + cmd = 'l'; + m_commands.push_back(m_cmds[cmd]); + } + } else { + cmd = prev_cmd; + m_commands.push_back(m_cmds[cmd]); + } + } else { + throw hresult_invalid_argument(L"Unexpected character: " + first_char); + } + + bool absolute{IsUpper(cmd)}; + switch (cmd) { + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + break; + case 'h': + case 'H': + case 'v': + case 'V': + m_segmentData.push_back(ParseListNumber(i)); + break; + case 's': + case 'S': + case 'q': + case 'Q': + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + break; + case 'c': + case 'C': + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + break; + case 'a': + case 'A': + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseFlag(i)); + m_segmentData.push_back(ParseFlag(i)); + m_segmentData.push_back(ParseListNumber(i)); + m_segmentData.push_back(ParseListNumber(i)); + break; + case 'z': + case 'Z': + break; + default: + throw hresult_invalid_argument(L"Unexpected command."); + } + + if (is_implicit_move_to) { + if (absolute) { + prev_cmd = 'M'; + } else { + prev_cmd = 'm'; + } + } else { + prev_cmd = cmd; + } + } +} + +void PathView::SkipSpaces(size_t &index) { + while (index < m_d.length() && IsSpace(m_d.at(index))) { + ++index; + } +} + +void PathView::SkipDigits(size_t& index) { + while (index < m_d.length() && IsDigit(m_d.at(index))) { + ++index; + } +} + +void PathView::SkipListSeparator(size_t& index) { + if (index < m_d.length() && m_d.at(index) == ',') { + ++index; + } +} + +bool PathView::IsCommand(char const &cmd) { + return m_cmds.find(cmd) != m_cmds.end(); +} + +bool PathView::IsNumberStart(char const& c) { + return IsDigit(c) || c == '.' || c == '-' || c == '+'; +} + +bool PathView::IsDigit(char const &c) { + return std::isdigit(static_cast(c)); +} + +bool PathView::IsUpper(char const &c) { + return std::isupper(static_cast(c)); +} + +bool PathView::IsSpace(char const& c) { + return std::isspace(static_cast(c)); +} + +float PathView::ParseListNumber(size_t &index) { + if (index == m_d.length()) { + throw hresult_invalid_argument(L"Unexpected end."); + } + + float result{ParseNumber(index)}; + SkipSpaces(index); + SkipListSeparator(index); + + return result; +} + +float PathView::ParseNumber(size_t &index) { + SkipSpaces(index); + + if (index == m_d.length()) { + throw hresult_invalid_argument(L"Unexpected end."); + } + + size_t start = index; + char c = m_d.at(start); + + // Consume sign. + if (c == '-' || c == '+') { + ++index; + c = m_d.at(index); + } + + // Consume integer. + if (IsDigit(c)) { + SkipDigits(index); + if (index < m_d.length()) { + c = m_d.at(index); + } + } else if (c != '.') { + throw hresult_invalid_argument(L"Invalid number formating character."); + } + + // Consume fraction. + if (c == '.') { + ++index; + SkipDigits(index); + if (index < m_d.length()) { + c = m_d.at(index); + } + } + + if ((c == 'e' || c == 'E') && ((index + 1) < m_d.length())) { + char c2 = m_d.at(index + 1); + // Check for 'em'/'ex' + if (c2 != 'm' && c2 != 'x') { + ++index; + c = m_d.at(index); + + if (c == '+' || c == '-') { + ++index; + SkipDigits(index); + } else if (IsDigit(c)) { + SkipDigits(index); + } else { + throw hresult_invalid_argument(L"Invalid number formating character."); + } + } + } + + auto num{m_d.substr(start, index)}; + auto result{std::stof(num, nullptr)}; + + if (std::isinf(result) || std::isnan(result)) { + throw hresult_invalid_argument(L"Invalid number."); + } + + return result; +} + +float PathView::ParseFlag(size_t& index) { + SkipSpaces(index); + + char c = m_d.at(index); + switch (c) { + case '0': + case '1': { + ++index; + if (index < m_d.length() && m_d.at(index) == ',') { + ++index; + } + SkipSpaces(index); + break; + } + default: + throw hresult_invalid_argument(L"Unexpected flag."); + } + + return static_cast(c - '0'); +} + +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/PathView.h b/windows/RNSVG/PathView.h new file mode 100644 index 00000000..3458f0db --- /dev/null +++ b/windows/RNSVG/PathView.h @@ -0,0 +1,61 @@ +#pragma once + +#include "PathView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct PathView : PathViewT { + public: + PathView() = default; + + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + + private: + std::string m_d; + std::vector m_segmentData; + std::vector m_commands; + + std::map m_cmds{ + {'M', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::MoveAbsolute}, + {'m', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::MoveRelative}, + {'Z', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::ClosePath}, + {'z', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::ClosePath}, + {'L', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::LineAbsolute}, + {'l', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::LineRelative}, + {'H', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::HorizontalAbsolute}, + {'h', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::HorizontalRelative}, + {'V', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::VerticalAbsolute}, + {'v', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::VerticalRelative}, + {'C', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::CubicAbsolute}, + {'c', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::CubicRelative}, + {'S', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::CubicSmoothAbsolute}, + {'s', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::CubicSmoothRelative}, + {'Q', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::QuadraticAbsolute}, + {'q', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::QuadraticRelative}, + {'T', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::QuadraticSmoothAbsolute}, + {'t', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::QuadraticSmoothRelative}, + {'A', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::ArcAbsolute}, + {'a', Microsoft::Graphics::Canvas::Svg::CanvasSvgPathCommand::ArcRelative}, + }; + + void ParsePath(); + + // Parser helpers + void SkipSpaces(size_t &index); + void SkipDigits(size_t &index); + void SkipListSeparator(size_t &index); + bool IsCommand(char const &cmd); + bool IsNumberStart(char const &c); + bool IsDigit(char const &c); + bool IsUpper(char const &c); + bool IsSpace(char const &c); + float ParseListNumber(size_t &index); + float ParseNumber(size_t &index); + float ParseFlag(size_t &index); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct PathView : PathViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/PathViewManager.cpp b/windows/RNSVG/PathViewManager.cpp new file mode 100644 index 00000000..6e3777db --- /dev/null +++ b/windows/RNSVG/PathViewManager.cpp @@ -0,0 +1,31 @@ +#include "pch.h" +#include "PathViewManager.h" +#if __has_include("PathViewManager.g.cpp") +#include "PathViewManager.g.cpp" +#endif + +#include "PathViewManager.h" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +PathViewManager::PathViewManager() { + m_class = RNSVG::SVGClass::RNSVGPath; + m_name = L"RNSVGPath"; +} + +// IViewManagerWithNativeProperties +IMapView PathViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"d", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/PathViewManager.h b/windows/RNSVG/PathViewManager.h new file mode 100644 index 00000000..e35a25df --- /dev/null +++ b/windows/RNSVG/PathViewManager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "PathViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct PathViewManager : PathViewManagerT { + PathViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct PathViewManager : PathViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/PatternView.cpp b/windows/RNSVG/PatternView.cpp new file mode 100644 index 00000000..eb0ae1c3 --- /dev/null +++ b/windows/RNSVG/PatternView.cpp @@ -0,0 +1,134 @@ +#include "pch.h" +#include "PatternView.h" +#include "PatternView.g.cpp" + +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void PatternView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "x") { + m_x = SVGLength::From(propertyValue); + } else if (propertyName == "y") { + m_y = SVGLength::From(propertyValue); + } else if (propertyName == "width") { + m_width = SVGLength::From(propertyValue); + } else if (propertyName == "height") { + m_height = SVGLength::From(propertyValue); + } else if (propertyName == "patternUnits") { + m_patternUnits = Utils::JSValueAsBrushUnits(propertyValue); + } else if (propertyName == "patternContentUnits") { + m_patternContentUnits = Utils::JSValueAsBrushUnits(propertyValue, "userSpaceOnUse"); + } else if (propertyName == "patternTransform") { + m_transformSet = true; + m_transform = Utils::JSValueAsTransform(propertyValue); + + if (propertyValue.IsNull()) { + m_transformSet = false; + } + } else if (propertyName == "vbWidth") { + m_vbWidth = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "vbHeight") { + m_vbHeight = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "minX") { + m_minX = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "minY") { + m_minY = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "align") { + m_align = Utils::JSValueAsString(propertyValue); + } else if (propertyName == "meetOrSlice") { + m_meetOrSlice = Utils::GetMeetOrSlice(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); + + SaveDefinition(); + + if (auto const &root{SvgRoot()}) { + root.InvalidateCanvas(); + } +} + +void PatternView::UpdateBounds() { + if (m_patternUnits == "objectBoundingBox") { + Rect rect{GetAdjustedRect(m_bounds)}; + CreateBrush(rect); + } +} + +void PatternView::CreateBrush() { + auto const &canvas{SvgRoot().Canvas()}; + + Rect elRect{GetAdjustedRect({0, 0, canvas.Size().Width, canvas.Size().Height})}; + CreateBrush(elRect); +} + +void PatternView::CreateBrush(Windows::Foundation::Rect const &rect) { + auto const &canvas{SvgRoot().Canvas()}; + auto const &resourceCreator{canvas.try_as()}; + + if (auto const &cmdList{GetCommandList(rect)}) { + Brushes::CanvasImageBrush brush{resourceCreator, cmdList}; + + brush.SourceRectangle(rect); + brush.ExtendX(Microsoft::Graphics::Canvas::CanvasEdgeBehavior::Wrap); + brush.ExtendY(Microsoft::Graphics::Canvas::CanvasEdgeBehavior::Wrap); + + cmdList.Close(); + + m_brush = brush; + } +} + +Windows::Foundation::Rect PatternView::GetAdjustedRect(Windows::Foundation::Rect const &bounds) { + float x{Utils::GetAbsoluteLength(m_x, bounds.Width) + bounds.X}; + float y{Utils::GetAbsoluteLength(m_y, bounds.Height) + bounds.Y}; + float width{Utils::GetAbsoluteLength(m_width, bounds.Width)}; + float height{Utils::GetAbsoluteLength(m_height, bounds.Height)}; + + return {x, y, width, height}; +} + +Microsoft::Graphics::Canvas::CanvasCommandList PatternView::GetCommandList(Windows::Foundation::Rect const &rect) { + auto const &root{SvgRoot()}; + auto const &canvas{root.Canvas()}; + auto const &cmdList{CanvasCommandList(canvas)}; + auto const &session{cmdList.CreateDrawingSession()}; + + if (m_align != "") { + Rect vbRect{ + m_minX * root.SvgScale(), + m_minY * root.SvgScale(), + (m_vbWidth + m_minX) * root.SvgScale(), + (m_vbHeight + m_minY) * root.SvgScale()}; + + auto transform{Utils::GetViewBoxTransform(vbRect, rect, m_align, m_meetOrSlice)}; + + if (m_transformSet) { + transform = transform * m_transform; + } + + session.Transform(transform); + } + + for (auto const &child : Children()) { + child.Render(canvas, session); + } + + session.Close(); + return cmdList; +} + + + +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/PatternView.h b/windows/RNSVG/PatternView.h new file mode 100644 index 00000000..4f04eb03 --- /dev/null +++ b/windows/RNSVG/PatternView.h @@ -0,0 +1,44 @@ +#pragma once +#include "PatternView.g.h" +#include "BrushView.h" + +namespace winrt::RNSVG::implementation { +struct PatternView : PatternViewT { + public: + PatternView() = default; + + // RenderableView + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + + private: + RNSVG::SVGLength m_x{}; + RNSVG::SVGLength m_y{}; + RNSVG::SVGLength m_width{}; + RNSVG::SVGLength m_height{}; + std::string m_patternUnits{"objectBoundingBox"}; + std::string m_patternContentUnits{"userSpaceOnUse"}; + bool m_transformSet{false}; + Numerics::float3x2 m_transform{Numerics::make_float3x2_scale(1)}; + + // ViewBox + float m_minX{0.0f}; + float m_minY{0.0f}; + float m_vbWidth{0.0f}; + float m_vbHeight{0.0f}; + std::string m_align{""}; + RNSVG::MeetOrSlice m_meetOrSlice{RNSVG::MeetOrSlice::Meet}; + + // BrushView + void CreateBrush(); + void UpdateBounds(); + + // Helpers + void CreateBrush(Windows::Foundation::Rect const &rect); + Windows::Foundation::Rect GetAdjustedRect(Windows::Foundation::Rect const &bounds); + Microsoft::Graphics::Canvas::CanvasCommandList GetCommandList(Windows::Foundation::Rect const &elRect); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct PatternView : PatternViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/PatternViewManager.cpp b/windows/RNSVG/PatternViewManager.cpp new file mode 100644 index 00000000..e7d60fd8 --- /dev/null +++ b/windows/RNSVG/PatternViewManager.cpp @@ -0,0 +1,38 @@ +#include "pch.h" +#include "PatternViewManager.h" +#include "PatternViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +PatternViewManager::PatternViewManager() { + m_class = RNSVG::SVGClass::RNSVGPattern; + m_name = L"RNSVGPattern"; +} + +IMapView PatternViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"x", ViewManagerPropertyType::String); + nativeProps.Insert(L"y", ViewManagerPropertyType::String); + nativeProps.Insert(L"width", ViewManagerPropertyType::String); + nativeProps.Insert(L"height", ViewManagerPropertyType::String); + nativeProps.Insert(L"patternUnits", ViewManagerPropertyType::Number); + nativeProps.Insert(L"patternContentUnits", ViewManagerPropertyType::Number); + nativeProps.Insert(L"patternTransform", ViewManagerPropertyType::Array); + nativeProps.Insert(L"minX", ViewManagerPropertyType::Number); + nativeProps.Insert(L"minY", ViewManagerPropertyType::Number); + nativeProps.Insert(L"vbWidth", ViewManagerPropertyType::Number); + nativeProps.Insert(L"vbHeight", ViewManagerPropertyType::Number); + nativeProps.Insert(L"align", ViewManagerPropertyType::String); + nativeProps.Insert(L"meetOrSlice", ViewManagerPropertyType::Number); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/PatternViewManager.h b/windows/RNSVG/PatternViewManager.h new file mode 100644 index 00000000..5805a696 --- /dev/null +++ b/windows/RNSVG/PatternViewManager.h @@ -0,0 +1,16 @@ +#pragma once +#include "PatternViewManager.g.h" +#include "GroupViewManager.h" + +namespace winrt::RNSVG::implementation { +struct PatternViewManager : PatternViewManagerT { + PatternViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct PatternViewManager : PatternViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/PropertySheet.props b/windows/RNSVG/PropertySheet.props new file mode 100644 index 00000000..5942ba39 --- /dev/null +++ b/windows/RNSVG/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/windows/RNSVG/RNSVG.def b/windows/RNSVG/RNSVG.def new file mode 100644 index 00000000..24e7c123 --- /dev/null +++ b/windows/RNSVG/RNSVG.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/windows/RNSVG/RNSVG.vcxproj b/windows/RNSVG/RNSVG.vcxproj new file mode 100644 index 00000000..92c6b162 --- /dev/null +++ b/windows/RNSVG/RNSVG.vcxproj @@ -0,0 +1,240 @@ + + + + + true + true + true + {7acf84ec-efba-4043-8e14-40b159508902} + RNSVG + RNSVG + en-US + 16.0 + true + Windows Store + 10.0 + 10.0.18362.0 + 10.0.16299.0 + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\ + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + DynamicLibrary + Unicode + false + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + 4453;28204 + _WINRT_DLL;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + true + RNSVG.def + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ReactPackageProvider.idl + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + ReactPackageProvider.idl + + + + + + + + + + + + + + + + + + + + + + + Designer + + + Designer + + + + + + + + + + + + + This project references targets in your node_modules\react-native-windows folder that are missing. The missing file is {0}. + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/windows/RNSVG/RNSVG.vcxproj.filters b/windows/RNSVG/RNSVG.vcxproj.filters new file mode 100644 index 00000000..33559813 --- /dev/null +++ b/windows/RNSVG/RNSVG.vcxproj.filters @@ -0,0 +1,268 @@ + + + + + + IDLs + + + IDLs + + + IDLs + + + + + + + + Views + + + Utils + + + ViewManagers + + + ViewManagers + + + Views + + + Views\GroupViews + + + Views\GroupViews + + + Views\GroupViews + + + Views + + + ViewManagers + + + ViewManagers + + + Views + + + ViewManagers + + + Views + + + Views + + + Views + + + Views\GroupViews + + + ViewManagers + + + ViewManagers + + + ViewManagers\GroupViewManagers + + + ViewManagers\GroupViewManagers + + + ViewManagers\GroupViewManagers + + + Views + + + ViewManagers\GroupViewManagers + + + ViewManagers + + + ViewManagers\GroupViewManagers + + + ViewManagers\GroupViewManagers + + + Views\GroupViews + + + Views\GroupViews + + + Views + + + ViewManagers\GroupViewManagers + + + Views\GroupViews + + + Views + + + ViewManagers + + + Views\GroupViews + + + ViewManagers\GroupViewManagers + + + + + + + + Views + + + Utils + + + Utils + + + ViewManagers + + + ViewManagers + + + Views + + + Views\GroupViews + + + Views\GroupViews + + + Views\GroupViews + + + Views + + + ViewManagers + + + ViewManagers + + + Views + + + ViewManagers + + + Views + + + Views + + + Views + + + Views\GroupViews + + + ViewManagers + + + ViewManagers + + + ViewManagers\GroupViewManagers + + + ViewManagers\GroupViewManagers + + + ViewManagers\GroupViewManagers + + + Views + + + ViewManagers\GroupViewManagers + + + ViewManagers + + + ViewManagers\GroupViewManagers + + + ViewManagers\GroupViewManagers + + + Views\GroupViews + + + Views\GroupViews + + + Views + + + ViewManagers\GroupViewManagers + + + Views\GroupViews + + + Views + + + ViewManagers + + + Views\GroupViews + + + ViewManagers\GroupViewManagers + + + + + + + + + {e902587e-fe1d-4643-875b-d58996b40d98} + + + {cb3ce7b2-9fdf-4578-8e0d-4d6853b727bc} + + + {8a4e0664-0d52-46ca-b476-a1ad90239a09} + + + {96f26034-8559-4794-a806-4ecac541e02b} + + + {5942afc3-8b5d-4d8c-bb09-e82581f606b7} + + + {50f81ae3-d543-477a-a60c-a0f310289f29} + + + \ No newline at end of file diff --git a/windows/RNSVG/RNSVGModule.h b/windows/RNSVG/RNSVGModule.h new file mode 100644 index 00000000..053eaf7c --- /dev/null +++ b/windows/RNSVG/RNSVGModule.h @@ -0,0 +1,33 @@ +#pragma once + +#include "JSValue.h" +#include "NativeModules.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::RNSVG +{ + REACT_MODULE(RNSVGModule, L"RNSVG") + struct RNSVGModule + { + // See https://microsoft.github.io/react-native-windows/docs/native-modules for details on writing native modules + + REACT_INIT(Initialize) + void Initialize(ReactContext const &reactContext) noexcept + { + m_reactContext = reactContext; + } + + REACT_METHOD(sampleMethod) + void + sampleMethod(std::string stringArgument, int numberArgument, std::function &&callback) noexcept + { + // TODO: Implement some actually useful functionality + callback("Received numberArgument: " + std::to_string(numberArgument) + " stringArgument: " + stringArgument); + } + + private: + ReactContext m_reactContext{nullptr}; + }; + +} // namespace winrt::RNSVG diff --git a/windows/RNSVG/RadialGradientView.cpp b/windows/RNSVG/RadialGradientView.cpp new file mode 100644 index 00000000..ce3dbb5d --- /dev/null +++ b/windows/RNSVG/RadialGradientView.cpp @@ -0,0 +1,92 @@ +#include "pch.h" +#include "RadialGradientView.h" +#include "RadialGradientView.g.cpp" + +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void RadialGradientView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "fx") { + m_fx = SVGLength::From(propertyValue); + } else if (propertyName == "fy") { + m_fy = SVGLength::From(propertyValue); + } else if (propertyName == "rx") { + m_rx = SVGLength::From(propertyValue); + } else if (propertyName == "ry") { + m_ry = SVGLength::From(propertyValue); + } else if (propertyName == "cx") { + m_cx = SVGLength::From(propertyValue); + } else if (propertyName == "cy") { + m_cy = SVGLength::From(propertyValue); + } else if (propertyName == "gradient") { + m_stops = Utils::JSValueAsStops(propertyValue); + } else if (propertyName == "gradientUnits") { + m_gradientUnits = Utils::JSValueAsBrushUnits(propertyValue); + } else if (propertyName == "gradientTransform") { + m_transformSet = true; + m_transform = Utils::JSValueAsTransform(propertyValue); + + if (propertyValue.IsNull()) { + m_transformSet = false; + } + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); + + SaveDefinition(); +} + +void RadialGradientView::Unload() { + m_stops.clear(); + __super::Unload(); +} + +void RadialGradientView::CreateBrush() { + auto const &canvas{SvgRoot().Canvas()}; + auto const &resourceCreator{canvas.try_as()}; + Brushes::CanvasRadialGradientBrush brush{resourceCreator, m_stops}; + + SetPoints(brush, {0, 0, canvas.Size().Width, canvas.Size().Height}); + + if (m_transformSet) { + brush.Transform(m_transform); + } + + m_brush = brush; +} + +void RadialGradientView::UpdateBounds() { + if (m_gradientUnits == "objectBoundingBox") { + SetPoints(m_brush.as(), m_bounds); + } +} + +void RadialGradientView::SetPoints(Brushes::CanvasRadialGradientBrush brush, Windows::Foundation::Rect const &bounds) { + float rx{Utils::GetAbsoluteLength(m_rx, bounds.Width)}; + float ry{Utils::GetAbsoluteLength(m_ry, bounds.Height)}; + + float fx{Utils::GetAbsoluteLength(m_fx, bounds.Width) + bounds.X}; + float fy{Utils::GetAbsoluteLength(m_fy, bounds.Height) + bounds.Y}; + + float cx{Utils::GetAbsoluteLength(m_cx, bounds.Width) + bounds.X}; + float cy{Utils::GetAbsoluteLength(m_cy, bounds.Height) + bounds.Y}; + + brush.RadiusX(rx); + brush.RadiusY(ry); + + brush.Center({cx, cy}); + brush.OriginOffset({(fx - cx), (fy - cy)}); +} + +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/RadialGradientView.h b/windows/RNSVG/RadialGradientView.h new file mode 100644 index 00000000..975356e5 --- /dev/null +++ b/windows/RNSVG/RadialGradientView.h @@ -0,0 +1,34 @@ +#pragma once +#include "RadialGradientView.g.h" +#include "BrushView.h" + +namespace winrt::RNSVG::implementation { +struct RadialGradientView : RadialGradientViewT { + public: + RadialGradientView() = default; + + // RenderableView + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void Unload(); + + private: + RNSVG::SVGLength m_fx{}; + RNSVG::SVGLength m_fy{}; + RNSVG::SVGLength m_rx{}; + RNSVG::SVGLength m_ry{}; + RNSVG::SVGLength m_cx{}; + RNSVG::SVGLength m_cy{}; + std::vector m_stops{}; + std::string m_gradientUnits{"objectBoundingBox"}; + bool m_transformSet{false}; + Numerics::float3x2 m_transform{Numerics::make_float3x2_scale(1)}; + + void CreateBrush(); + void UpdateBounds(); + void SetPoints(Microsoft::Graphics::Canvas::Brushes::CanvasRadialGradientBrush brush, Windows::Foundation::Rect const &bounds); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct RadialGradientView : RadialGradientViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/RadialGradientViewManager.cpp b/windows/RNSVG/RadialGradientViewManager.cpp new file mode 100644 index 00000000..fb63bf98 --- /dev/null +++ b/windows/RNSVG/RadialGradientViewManager.cpp @@ -0,0 +1,34 @@ +#include "pch.h" +#include "RadialGradientViewManager.h" +#include "RadialGradientViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +RadialGradientViewManager::RadialGradientViewManager() { + m_class = RNSVG::SVGClass::RNSVGRadialGradient; + m_name = L"RNSVGRadialGradient"; +} + +IMapView RadialGradientViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"fx", ViewManagerPropertyType::String); + nativeProps.Insert(L"fy", ViewManagerPropertyType::String); + nativeProps.Insert(L"rx", ViewManagerPropertyType::String); + nativeProps.Insert(L"ry", ViewManagerPropertyType::String); + nativeProps.Insert(L"cx", ViewManagerPropertyType::String); + nativeProps.Insert(L"cy", ViewManagerPropertyType::String); + nativeProps.Insert(L"gradient", ViewManagerPropertyType::Array); + nativeProps.Insert(L"gradientUnits", ViewManagerPropertyType::Number); + nativeProps.Insert(L"gradientTransform", ViewManagerPropertyType::Array); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/RadialGradientViewManager.h b/windows/RNSVG/RadialGradientViewManager.h new file mode 100644 index 00000000..def130c5 --- /dev/null +++ b/windows/RNSVG/RadialGradientViewManager.h @@ -0,0 +1,18 @@ +#pragma once +#include "RadialGradientViewManager.g.h" +#include "GroupViewManager.h" + +namespace winrt::RNSVG::implementation { +struct RadialGradientViewManager + : RadialGradientViewManagerT { + RadialGradientViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct RadialGradientViewManager + : RadialGradientViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/ReactPackageProvider.cpp b/windows/RNSVG/ReactPackageProvider.cpp new file mode 100644 index 00000000..7b20d34e --- /dev/null +++ b/windows/RNSVG/ReactPackageProvider.cpp @@ -0,0 +1,50 @@ +#include "pch.h" +#include "ReactPackageProvider.h" +#if __has_include("ReactPackageProvider.g.cpp") +#include "ReactPackageProvider.g.cpp" +#endif + +#include "RNSVGModule.h" +#include "SvgViewManager.h" +#include "GroupViewManager.h" +#include "PathViewManager.h" +#include "RectViewManager.h" +#include "CircleViewManager.h" +#include "EllipseViewManager.h" +#include "LineViewManager.h" +#include "UseViewManager.h" +#include "ImageViewManager.h" +#include "TextViewManager.h" +#include "TSpanViewManager.h" +#include "SymbolViewManager.h" +#include "DefsViewManager.h" +#include "LinearGradientViewManager.h" +#include "RadialGradientViewManager.h" +#include "PatternViewManager.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation +{ + void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept + { + AddAttributedModules(packageBuilder); + packageBuilder.AddViewManager(L"SvgViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"GroupViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"PathViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"RectViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"CircleViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"EllipseViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"LineViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"UseViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"ImageViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"TextViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"TSpanViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"SymbolViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"DefsViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"LinearGradientViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"RadialGradientViewManager", []() { return winrt::make(); }); + packageBuilder.AddViewManager(L"PatternViewManager", []() { return winrt::make(); }); + } + +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/ReactPackageProvider.h b/windows/RNSVG/ReactPackageProvider.h new file mode 100644 index 00000000..c26f3631 --- /dev/null +++ b/windows/RNSVG/ReactPackageProvider.h @@ -0,0 +1,21 @@ +#pragma once +#include "ReactPackageProvider.g.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation +{ + struct ReactPackageProvider : ReactPackageProviderT + { + ReactPackageProvider() = default; + + void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept; + }; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation +{ + +struct ReactPackageProvider : ReactPackageProviderT {}; + +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/ReactPackageProvider.idl b/windows/RNSVG/ReactPackageProvider.idl new file mode 100644 index 00000000..da4f37fe --- /dev/null +++ b/windows/RNSVG/ReactPackageProvider.idl @@ -0,0 +1,9 @@ +namespace RNSVG +{ + [webhosthidden] + [default_interface] + runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider + { + ReactPackageProvider(); + }; +} diff --git a/windows/RNSVG/RectView.cpp b/windows/RNSVG/RectView.cpp new file mode 100644 index 00000000..d4f701f9 --- /dev/null +++ b/windows/RNSVG/RectView.cpp @@ -0,0 +1,55 @@ +#include "pch.h" +#include "RectView.h" +#if __has_include("RectView.g.cpp") +#include "RectView.g.cpp" +#endif + +#include "JSValueXaml.h" +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void RectView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "width") { + m_width = SVGLength::From(propertyValue); + } else if (propertyName == "height") { + m_height = SVGLength::From(propertyValue); + } else if (propertyName == "x") { + m_x = SVGLength::From(propertyValue); + } else if (propertyName == "y") { + m_y = SVGLength::From(propertyValue); + } else if (propertyName == "rx") { + m_rx = SVGLength::From(propertyValue); + } else if (propertyName == "ry") { + m_ry = SVGLength::From(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void RectView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + + float x{Utils::GetAbsoluteLength(m_x, canvas.Size().Width)}; + float y{Utils::GetAbsoluteLength(m_y, canvas.Size().Height)}; + float width{Utils::GetAbsoluteLength(m_width, canvas.Size().Width)}; + float height{Utils::GetAbsoluteLength(m_height, canvas.Size().Height)}; + + auto const &rxLength{m_rx.Unit() == RNSVG::LengthType::Unknown ? m_ry : m_rx}; + auto const &ryLength{m_ry.Unit() == RNSVG::LengthType::Unknown ? m_rx : m_ry}; + float rx{Utils::GetAbsoluteLength(rxLength, canvas.Size().Width)}; + float ry{Utils::GetAbsoluteLength(ryLength, canvas.Size().Height)}; + + Geometry(Geometry::CanvasGeometry::CreateRoundedRectangle(resourceCreator, x, y, width, height, rx, ry)); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/RectView.h b/windows/RNSVG/RectView.h new file mode 100644 index 00000000..bc8c98e4 --- /dev/null +++ b/windows/RNSVG/RectView.h @@ -0,0 +1,26 @@ +#pragma once + +#include "RectView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct RectView : RectViewT { + public: + RectView() = default; + + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + + private: + RNSVG::SVGLength m_width{}; + RNSVG::SVGLength m_height{}; + RNSVG::SVGLength m_x{}; + RNSVG::SVGLength m_y{}; + RNSVG::SVGLength m_rx{}; + RNSVG::SVGLength m_ry{}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct RectView : RectViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/RectViewManager.cpp b/windows/RNSVG/RectViewManager.cpp new file mode 100644 index 00000000..07813d3a --- /dev/null +++ b/windows/RNSVG/RectViewManager.cpp @@ -0,0 +1,36 @@ +#include "pch.h" +#include "RectViewManager.h" +#if __has_include("RectViewManager.g.cpp") +#include "RectViewManager.g.cpp" +#endif + +#include "RectView.h" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +RectViewManager::RectViewManager() { + m_class = RNSVG::SVGClass::RNSVGRect; + m_name = L"RNSVGRect"; +} + +// IViewManagerWithNativeProperties +IMapView RectViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"height", ViewManagerPropertyType::String); + nativeProps.Insert(L"width", ViewManagerPropertyType::String); + nativeProps.Insert(L"x", ViewManagerPropertyType::String); + nativeProps.Insert(L"y", ViewManagerPropertyType::String); + nativeProps.Insert(L"rx", ViewManagerPropertyType::String); + nativeProps.Insert(L"ry", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/RectViewManager.h b/windows/RNSVG/RectViewManager.h new file mode 100644 index 00000000..f16da3bd --- /dev/null +++ b/windows/RNSVG/RectViewManager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "RectViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct RectViewManager : RectViewManagerT { + RectViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct RectViewManager : RectViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/RenderableView.cpp b/windows/RNSVG/RenderableView.cpp new file mode 100644 index 00000000..f91da5e1 --- /dev/null +++ b/windows/RNSVG/RenderableView.cpp @@ -0,0 +1,341 @@ +#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::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void RenderableView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + auto const &parent{SvgParent().try_as()}; + + 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]) { + if (propertyValue.Type() == JSValueType::Array) { + auto const &brush{propertyValue.AsArray()}; + m_strokeBrushId = to_hstring(Utils::JSValueAsString(brush.at(1))); + } else { + Windows::UI::Color fallbackColor{(parent && !strokeSet) ? parent.Stroke() : Windows::UI::Colors::Transparent()}; + m_stroke = Utils::JSValueAsColor(propertyValue, fallbackColor); + } + } + } else if (propertyName == "fill") { + prop = RNSVG::BaseProp::Fill; + if (forceUpdate || !m_propSetMap[prop]) { + if (propertyValue.Type() == JSValueType::Array) { + auto const &brush{propertyValue.AsArray()}; + m_fillBrushId = to_hstring(Utils::JSValueAsString(brush.at(1))); + } else { + Windows::UI::Color fallbackColor{Windows::UI::Colors::Black()}; + if (propertyValue.IsNull() && fillSet) { + fallbackColor = Windows::UI::Colors::Transparent(); + } else if (parent) { + fallbackColor = parent.Fill(); + } + + m_fill = Utils::JSValueAsColor(propertyValue, fallbackColor); + } + } + } else if (propertyName == "strokeLinecap") { + prop = RNSVG::BaseProp::StrokeLineCap; + if (forceUpdate || !m_propSetMap[prop]) { + if (propertyValue.IsNull()) { + m_strokeLineCap = parent.StrokeLineCap(); + } else { + auto const &strokeLineCap{propertyValue.AsInt32()}; + switch (strokeLineCap) { + case 2: + m_strokeLineCap = Geometry::CanvasCapStyle::Square; + break; + case 1: + m_strokeLineCap = Geometry::CanvasCapStyle::Round; + break; + case 0: + default: + m_strokeLineCap = Geometry::CanvasCapStyle::Flat; + break; + } + } + } + } else if (propertyName == "strokeLinejoin") { + prop = RNSVG::BaseProp::StrokeLineJoin; + if (forceUpdate || !m_propSetMap[prop]) { + if (propertyValue.IsNull()) { + m_strokeLineCap = parent.StrokeLineCap(); + } else { + auto const &strokeLineJoin{propertyValue.AsInt32()}; + switch (strokeLineJoin) { + case 2: + m_strokeLineJoin = Geometry::CanvasLineJoin::Bevel; + break; + case 1: + m_strokeLineJoin = Geometry::CanvasLineJoin::Round; + break; + case 0: + default: + m_strokeLineJoin = Geometry::CanvasLineJoin::Miter; + break; + } + } + } + } else if (propertyName == "fillRule") { + prop = RNSVG::BaseProp::FillRule; + if (forceUpdate || !m_propSetMap[prop]) { + if (propertyValue.IsNull()) { + m_fillRule = parent.FillRule(); + } else { + auto const &fillRule{propertyValue.AsInt32()}; + switch (fillRule) { + case 0: + m_fillRule = Geometry::CanvasFilledRegionDetermination::Alternate; + break; + case 1: + default: + m_fillRule = Geometry::CanvasFilledRegionDetermination::Winding; + break; + } + } + } + } 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(SVGLength::From(item)); + } + } + } + } + } 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); + } + + // 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().InvalidateCanvas(); + } +} + +void RenderableView::SaveDefinition() { + if (m_id != L"") { + SvgRoot().Templates().Insert(m_id, *this); + } +} + +void RenderableView::Render(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + auto const &resourceCreator{canvas.try_as()}; + if (m_recreateResources) { + CreateGeometry(canvas); + } + + auto geometry{Geometry()}; + if (m_propSetMap[RNSVG::BaseProp::Matrix]) { + geometry = geometry.Transform(SvgTransform()); + } + + geometry = Geometry::CanvasGeometry::CreateGroup(resourceCreator, {geometry}, FillRule()); + + if (auto const &opacityLayer{session.CreateLayer(m_opacity)}) { + if (auto const &fillLayer{session.CreateLayer(FillOpacity())}) { + auto const &fill{Utils::GetCanvasBrush(FillBrushId(), Fill(), SvgRoot(), geometry, resourceCreator)}; + session.FillGeometry(geometry, fill); + fillLayer.Close(); + } + + if (auto const &strokeLayer{session.CreateLayer(StrokeOpacity())}) { + Geometry::CanvasStrokeStyle strokeStyle{}; + strokeStyle.StartCap(StrokeLineCap()); + strokeStyle.EndCap(StrokeLineCap()); + strokeStyle.LineJoin(StrokeLineJoin()); + strokeStyle.DashOffset(StrokeDashOffset()); + strokeStyle.MiterLimit(StrokeMiterLimit()); + + float canvasDiagonal{Utils::GetCanvasDiagonal(canvas.Size())}; + float strokeWidth{Utils::GetAbsoluteLength(StrokeWidth(), canvasDiagonal)}; + strokeStyle.CustomDashStyle(Utils::GetAdjustedStrokeArray(StrokeDashArray(), strokeWidth, canvasDiagonal)); + + auto const &stroke{Utils::GetCanvasBrush(StrokeBrushId(), Stroke(), SvgRoot(), geometry, resourceCreator)}; + session.DrawGeometry(geometry, stroke, strokeWidth, strokeStyle); + strokeLayer.Close(); + } + + opacityLayer.Close(); + } +} + +void RenderableView::MergeProperties(RNSVG::RenderableView const &other) { + for (auto const &prop : m_propSetMap) { + if (!prop.second) { + switch (prop.first) { + case RNSVG::BaseProp::Fill: + m_fill = other.Fill(); + m_fillBrushId = other.FillBrushId(); + break; + case RNSVG::BaseProp::FillOpacity: + m_fillOpacity = other.FillOpacity(); + break; + case RNSVG::BaseProp::FillRule: + m_fillRule = other.FillRule(); + break; + case RNSVG::BaseProp::Stroke: + m_stroke = other.Stroke(); + m_strokeBrushId = other.StrokeBrushId(); + break; + case RNSVG::BaseProp::StrokeOpacity: + m_strokeOpacity = other.StrokeOpacity(); + break; + case RNSVG::BaseProp::StrokeWidth: + m_strokeWidth = other.StrokeWidth(); + break; + case RNSVG::BaseProp::StrokeMiterLimit: + m_strokeMiterLimit = other.StrokeMiterLimit(); + break; + case RNSVG::BaseProp::StrokeDashOffset: + m_strokeDashOffset = other.StrokeDashOffset(); + break; + case RNSVG::BaseProp::StrokeDashArray: + m_strokeDashArray = other.StrokeDashArray(); + break; + case RNSVG::BaseProp::StrokeLineCap: + m_strokeLineCap = other.StrokeLineCap(); + break; + case RNSVG::BaseProp::StrokeLineJoin: + m_strokeLineJoin = other.StrokeLineJoin(); + break; + case RNSVG::BaseProp::Unknown: + default: + break; + } + } + } +} + +RNSVG::SvgView RenderableView::SvgRoot() { + if (SvgParent()) { + if (auto const &svgView{SvgParent().try_as()}) { + if (svgView.SvgParent()) { + if (auto const &parent{svgView.SvgParent().try_as()}) { + return parent.SvgRoot(); + } + } else { + return svgView; + } + } else if (auto const &renderable{SvgParent().try_as()}) { + return renderable.SvgRoot(); + } + } + + return nullptr; +} + +void RenderableView::Unload() { + if (m_geometry) { + m_geometry.Close(); + m_geometry = nullptr; + } + + m_parent = nullptr; + m_reactContext = nullptr; + m_propList.clear(); + m_propSetMap.clear(); + m_strokeDashArray.Clear(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/RenderableView.h b/windows/RNSVG/RenderableView.h new file mode 100644 index 00000000..48241cf7 --- /dev/null +++ b/windows/RNSVG/RenderableView.h @@ -0,0 +1,97 @@ +#pragma once + +#include "RenderableView.g.h" +#include "SVGLength.h" + +namespace winrt::RNSVG::implementation { +struct RenderableView : RenderableViewT { + public: + RenderableView() = default; + RenderableView(Microsoft::ReactNative::IReactContext const &context) : m_reactContext(context) {} + + RNSVG::SvgView SvgRoot(); + + Windows::UI::Xaml::FrameworkElement SvgParent() { return m_parent; } + void SvgParent(Windows::UI::Xaml::FrameworkElement const &value) { m_parent = value; } + + Microsoft::Graphics::Canvas::Geometry::CanvasGeometry Geometry() { return m_geometry; } + void Geometry(Microsoft::Graphics::Canvas::Geometry::CanvasGeometry value) { m_geometry = value; } + + hstring Id() { return m_id; } + Numerics::float3x2 SvgTransform() { return m_transformMatrix; } + + hstring FillBrushId() { return m_fillBrushId; } + Windows::UI::Color Fill() { return m_fill; } + float FillOpacity() { return m_fillOpacity; } + hstring StrokeBrushId() { return m_strokeBrushId; } + Windows::UI::Color Stroke() { return m_stroke; } + float StrokeOpacity() { return m_strokeOpacity; } + float StrokeMiterLimit() { return m_strokeMiterLimit; } + float StrokeDashOffset() { return m_strokeDashOffset; } + RNSVG::SVGLength StrokeWidth() { return m_strokeWidth; } + Windows::Foundation::Collections::IVector StrokeDashArray() { return m_strokeDashArray; } + Microsoft::Graphics::Canvas::Geometry::CanvasCapStyle StrokeLineCap() { return m_strokeLineCap; } + Microsoft::Graphics::Canvas::Geometry::CanvasLineJoin StrokeLineJoin() { return m_strokeLineJoin; } + Microsoft::Graphics::Canvas::Geometry::CanvasFilledRegionDetermination FillRule() { return m_fillRule; } + + virtual void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate = true, bool invalidate = true); + virtual void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const & /*canvas*/) {} + virtual void MergeProperties(RNSVG::RenderableView const &other); + virtual void SaveDefinition(); + virtual void Unload(); + virtual void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + virtual void CreateResources( + Microsoft::Graphics::Canvas::ICanvasResourceCreator const & /*resourceCreator*/, + Microsoft::Graphics::Canvas::UI::CanvasCreateResourcesEventArgs const & /*args*/) { } + + protected: + float m_opacity{1.0f}; + std::vector m_propList{}; + std::map m_propSetMap{ + {RNSVG::BaseProp::Matrix, false}, + {RNSVG::BaseProp::Fill, false}, + {RNSVG::BaseProp::FillOpacity, false}, + {RNSVG::BaseProp::FillRule, false}, + {RNSVG::BaseProp::Stroke, false}, + {RNSVG::BaseProp::StrokeOpacity, false}, + {RNSVG::BaseProp::StrokeWidth, false}, + {RNSVG::BaseProp::StrokeMiterLimit, false}, + {RNSVG::BaseProp::StrokeDashOffset, false}, + {RNSVG::BaseProp::StrokeDashArray, false}, + {RNSVG::BaseProp::StrokeLineCap, false}, + {RNSVG::BaseProp::StrokeLineJoin, false}, + }; + + private: + Microsoft::ReactNative::IReactContext m_reactContext{nullptr}; + Windows::UI::Xaml::FrameworkElement m_parent{nullptr}; + Microsoft::Graphics::Canvas::Geometry::CanvasGeometry m_geometry{nullptr}; + bool m_recreateResources{true}; + + hstring m_id{L""}; + Numerics::float3x2 m_transformMatrix{Numerics::make_float3x2_rotation(0)}; + Windows::UI::Color m_fill{Windows::UI::Colors::Black()}; + Windows::UI::Color m_stroke{Windows::UI::Colors::Transparent()}; + hstring m_fillBrushId{L""}; + hstring m_strokeBrushId{L""}; + float m_fillOpacity{1.0f}; + float m_strokeOpacity{1.0f}; + float m_strokeMiterLimit{0.0f}; + float m_strokeDashOffset{0.0f}; + RNSVG::SVGLength m_strokeWidth{1.0f, RNSVG::LengthType::Pixel}; + Windows::Foundation::Collections::IVector m_strokeDashArray{ + winrt::single_threaded_vector()}; + Microsoft::Graphics::Canvas::Geometry::CanvasCapStyle m_strokeLineCap{ + Microsoft::Graphics::Canvas::Geometry::CanvasCapStyle::Flat}; + Microsoft::Graphics::Canvas::Geometry::CanvasLineJoin m_strokeLineJoin{ + Microsoft::Graphics::Canvas::Geometry::CanvasLineJoin::Miter}; + Microsoft::Graphics::Canvas::Geometry::CanvasFilledRegionDetermination m_fillRule{ + Microsoft::Graphics::Canvas::Geometry::CanvasFilledRegionDetermination::Winding}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct RenderableView : RenderableViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/RenderableViewManager.cpp b/windows/RNSVG/RenderableViewManager.cpp new file mode 100644 index 00000000..48465721 --- /dev/null +++ b/windows/RNSVG/RenderableViewManager.cpp @@ -0,0 +1,78 @@ +#include "pch.h" +#include "RenderableViewManager.h" +#if __has_include("RenderableViewManager.g.cpp") +#include "RenderableViewManager.g.cpp" +#endif + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +Windows::UI::Xaml::FrameworkElement RenderableViewManager::CreateView() { + switch (m_class) { + case RNSVG::SVGClass::RNSVGGroup: + return winrt::RNSVG::GroupView(m_reactContext); + case RNSVG::SVGClass::RNSVGPath: + return winrt::RNSVG::PathView(); + case RNSVG::SVGClass::RNSVGRect: + return winrt::RNSVG::RectView(); + case RNSVG::SVGClass::RNSVGCircle: + return winrt::RNSVG::CircleView(); + case RNSVG::SVGClass::RNSVGEllipse: + return winrt::RNSVG::EllipseView(); + case RNSVG::SVGClass::RNSVGLine: + return winrt::RNSVG::LineView(); + case RNSVG::SVGClass::RNSVGUse: + return winrt::RNSVG::UseView(); + case RNSVG::SVGClass::RNSVGImage: + return winrt::RNSVG::ImageView(); + case RNSVG::SVGClass::RNSVGText: + return winrt::RNSVG::TextView(); + case RNSVG::SVGClass::RNSVGTSpan: + return winrt::RNSVG::TSpanView(); + case RNSVG::SVGClass::RNSVGSymbol: + return winrt::RNSVG::SymbolView(); + case RNSVG::SVGClass::RNSVGDefs: + return winrt::RNSVG::DefsView(); + case RNSVG::SVGClass::RNSVGLinearGradient: + return winrt::RNSVG::LinearGradientView(); + case RNSVG::SVGClass::RNSVGRadialGradient: + return winrt::RNSVG::RadialGradientView(); + case RNSVG::SVGClass::RNSVGPattern: + return winrt::RNSVG::PatternView(); + } + + throw hresult_not_implemented(); +} + +// IViewManagerWithNativeProperties +IMapView RenderableViewManager::NativeProps() { + auto const &nativeProps{winrt::single_threaded_map()}; + + nativeProps.Insert(L"name", ViewManagerPropertyType::String); + nativeProps.Insert(L"fill", ViewManagerPropertyType::Number); + nativeProps.Insert(L"fillRule", ViewManagerPropertyType::Number); + nativeProps.Insert(L"fillOpacity", ViewManagerPropertyType::Number); + nativeProps.Insert(L"stroke", ViewManagerPropertyType::Number); + nativeProps.Insert(L"strokeOpacity", ViewManagerPropertyType::Number); + nativeProps.Insert(L"strokeWidth", ViewManagerPropertyType::String); + nativeProps.Insert(L"strokeLinecap", ViewManagerPropertyType::Number); + nativeProps.Insert(L"strokeLinejoin", ViewManagerPropertyType::Number); + nativeProps.Insert(L"strokeMiterlimit", ViewManagerPropertyType::Number); + nativeProps.Insert(L"strokeDashoffset", ViewManagerPropertyType::Number); + nativeProps.Insert(L"strokeDasharray", ViewManagerPropertyType::Array); + nativeProps.Insert(L"matrix", ViewManagerPropertyType::Array); + nativeProps.Insert(L"opacity", ViewManagerPropertyType::Number); + nativeProps.Insert(L"propList", ViewManagerPropertyType::Array); + + return nativeProps.GetView(); +} + +void RenderableViewManager::UpdateProperties( + Windows::UI::Xaml::FrameworkElement const &view, + Microsoft::ReactNative::IJSValueReader const &propertyMapReader) { + if (auto const &renderable{view.try_as()}) { + renderable->UpdateProperties(propertyMapReader); + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/RenderableViewManager.h b/windows/RNSVG/RenderableViewManager.h new file mode 100644 index 00000000..d0c58b82 --- /dev/null +++ b/windows/RNSVG/RenderableViewManager.h @@ -0,0 +1,35 @@ +#pragma once + +#include "RenderableViewManager.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct RenderableViewManager : RenderableViewManagerT { + public: + RenderableViewManager() = default; + + // IViewManager + hstring Name() { return m_name; } + Windows::UI::Xaml::FrameworkElement CreateView(); + + // IViewManagerWithReactContext + Microsoft::ReactNative::IReactContext ReactContext() { return m_reactContext; } + void ReactContext(Microsoft::ReactNative::IReactContext const &value) { m_reactContext = value; } + + // IViewManagerWithNativeProperties + void UpdateProperties( + Windows::UI::Xaml::FrameworkElement const &view, + Microsoft::ReactNative::IJSValueReader const &propertyMapReader); + virtual + Windows::Foundation::Collections::IMapView NativeProps(); + + protected: + Microsoft::ReactNative::IReactContext m_reactContext{nullptr}; + RNSVG::SVGClass m_class{RNSVG::SVGClass::Unknown}; + hstring m_name{}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct RenderableViewManager : RenderableViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/SVGLength.cpp b/windows/RNSVG/SVGLength.cpp new file mode 100644 index 00000000..0a63f4e8 --- /dev/null +++ b/windows/RNSVG/SVGLength.cpp @@ -0,0 +1,59 @@ +#include "pch.h" +#include "SVGLength.h" +#if __has_include("SVGLength.g.cpp") +#include "SVGLength.g.cpp" +#endif + +namespace winrt::RNSVG::implementation { +SVGLength::SVGLength(float value) : m_value(value), m_unit(RNSVG::LengthType::Number) {} + +SVGLength::SVGLength(float value, RNSVG::LengthType type) : m_value(value), m_unit(type) {} + +RNSVG::SVGLength SVGLength::From(std::string value) { + auto strLength{value.size()}; + if (strLength == 0 || value == "normal") { + return {0.0, RNSVG::LengthType::Unknown}; + } else if (value.back() == '%') { + return {std::stof(value.substr(0, strLength - 1), nullptr), RNSVG::LengthType::Percentage}; + } else if (strLength > 2) { + auto end{strLength - 2}; + auto lastTwo{value.substr(end)}; + + auto unit{RNSVG::LengthType::Unknown}; + if (lastTwo == "px") { + unit = RNSVG::LengthType::Number; + } else if (lastTwo == "em") { + unit = RNSVG::LengthType::EMS; + } else if (lastTwo == "ex") { + unit = RNSVG::LengthType::EXS; + } else if (lastTwo == "cm") { + unit = RNSVG::LengthType::Centimeter; + } else if (lastTwo == "mm") { + unit = RNSVG::LengthType::Millimeter; + } else if (lastTwo == "in") { + unit = RNSVG::LengthType::Inch; + } else if (lastTwo == "pt") { + unit = RNSVG::LengthType::Point; + } else if (lastTwo == "pc") { + unit = RNSVG::LengthType::Pica; + } else { + unit = RNSVG::LengthType::Number; + end = strLength; + } + + return {std::stof(value.substr(0, end), nullptr), unit}; + } + + return {std::stof(value, nullptr), RNSVG::LengthType::Number}; +} + +RNSVG::SVGLength SVGLength::From(Microsoft::ReactNative::JSValue const &propertyValue) { + if (propertyValue.IsNull()) { + return {0.0f, RNSVG::LengthType::Unknown}; + } else if (propertyValue.Type() == Microsoft::ReactNative::JSValueType::String) { + return SVGLength::From(propertyValue.AsString()); + } else { + return RNSVG::SVGLength(propertyValue.AsSingle()); + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/SVGLength.h b/windows/RNSVG/SVGLength.h new file mode 100644 index 00000000..b4916eef --- /dev/null +++ b/windows/RNSVG/SVGLength.h @@ -0,0 +1,28 @@ +#pragma once + +#include "SVGLength.g.h" + +#include "JSValueXaml.h" + +namespace winrt::RNSVG::implementation { +struct SVGLength : SVGLengthT { + public: + SVGLength() = default; + SVGLength(float value); + SVGLength(float value, RNSVG::LengthType type); + + float Value() { return m_value; } + RNSVG::LengthType Unit() { return m_unit; } + + static RNSVG::SVGLength From(std::string value); + static RNSVG::SVGLength From(Microsoft::ReactNative::JSValue const &value); + + private: + float m_value{0.0f}; + RNSVG::LengthType m_unit{RNSVG::LengthType::Unknown}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct SVGLength : SVGLengthT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/SvgView.cpp b/windows/RNSVG/SvgView.cpp new file mode 100644 index 00000000..771c4b90 --- /dev/null +++ b/windows/RNSVG/SvgView.cpp @@ -0,0 +1,194 @@ +#include "pch.h" + +#include "SvgView.h" +#if __has_include("SvgView.g.cpp") +#include "SvgView.g.cpp" +#endif + +#include + +#include "GroupView.h" +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; +using namespace Windows::Graphics::Display; + +namespace winrt::RNSVG::implementation { +SvgView::SvgView(IReactContext const &context) : m_reactContext(context) { + m_scale = static_cast(DisplayInformation::GetForCurrentView().ResolutionScale()) / 100; + + m_canvasDrawRevoker = m_canvas.Draw(winrt::auto_revoke, {get_weak(), &SvgView::Canvas_Draw}); + m_canvasCreateResourcesRevoker = m_canvas.CreateResources(winrt::auto_revoke, {get_weak(), &SvgView::Canvas_CreateResources}); + m_canvasSizeChangedRevoker = m_canvas.SizeChanged(winrt::auto_revoke, {get_weak(), &SvgView::Canvas_SizeChanged}); + m_panelUnloadedRevoker = Unloaded(winrt::auto_revoke, {get_weak(), &SvgView::Panel_Unloaded}); + + Children().Append(m_canvas); +} + +void SvgView::SvgParent(Windows::UI::Xaml::FrameworkElement const &value) { + if (value) { + m_canvasDrawRevoker.revoke(); + m_canvasCreateResourcesRevoker.revoke(); + m_canvasSizeChangedRevoker.revoke(); + m_panelUnloadedRevoker.revoke(); + m_canvas.RemoveFromVisualTree(); + m_canvas = nullptr; + m_parent = value; + } +} + +void SvgView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + // If forceUpdate is false, that means this is a nested Svg + // and we're inheriting props. Pass those along to the group. + if (!forceUpdate && m_group) { + m_group.UpdateProperties(reader, forceUpdate, invalidate); + } else { + auto const &propertyMap{JSValueObject::ReadFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "name") { + if (m_parent && m_group) { + m_group.SvgRoot().Templates().Remove(m_id); + } + m_id = to_hstring(Utils::JSValueAsString(propertyValue)); + if (m_parent) { + SaveDefinition(); + } + } else if (propertyName == "width") { + m_width = SVGLength::From(propertyValue); + } else if (propertyName == "height") { + m_height = SVGLength::From(propertyValue); + } else if (propertyName == "bbWidth") { + m_bbWidth = SVGLength::From(propertyValue); + Width(m_bbWidth.Value()); + } else if (propertyName == "bbHeight") { + m_bbHeight = SVGLength::From(propertyValue); + Height(m_bbHeight.Value()); + } else if (propertyName == "vbWidth") { + m_vbWidth = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "vbHeight") { + m_vbHeight = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "minX") { + m_minX = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "minY") { + m_minY = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "align") { + m_align = Utils::JSValueAsString(propertyValue); + } else if (propertyName == "meetOrSlice") { + m_meetOrSlice = Utils::GetMeetOrSlice(propertyValue); + } + } + + InvalidateCanvas(); + } +} + +void SvgView::SaveDefinition() { + if (m_id != L"" && m_group) { + m_group.SvgRoot().Templates().Insert(m_id, *this); + m_group.SaveDefinition(); + } +} + +void SvgView::MergeProperties(RNSVG::RenderableView const &other) { + if (m_group) { + m_group.MergeProperties(other); + } +} + +Size SvgView::MeasureOverride(Size availableSize) { + for (auto const &child : Children()) { + child.Measure(availableSize); + } + return availableSize; +} + +Size SvgView::ArrangeOverride(Size finalSize) { + for (auto const &child : Children()) { + child.Arrange({0, 0, finalSize.Width, finalSize.Height}); + } + return finalSize; +} + +void SvgView::Render(UI::Xaml::CanvasControl const & canvas, CanvasDrawingSession const & session) { + if (m_align != "") { + Rect vbRect{m_minX * m_scale, m_minY * m_scale, m_vbWidth * m_scale, m_vbHeight * m_scale}; + float width{static_cast(canvas.ActualWidth())}; + float height{static_cast(canvas.ActualHeight())}; + bool nested{m_parent}; + + if (nested) { + width = Utils::GetAbsoluteLength(m_bbWidth, width); + height = Utils::GetAbsoluteLength(m_bbHeight, height); + } + + Rect elRect{0, 0, width, height}; + + session.Transform(Utils::GetViewBoxTransform(vbRect, elRect, m_align, m_meetOrSlice)); + } + + if (m_group) { + m_group.SaveDefinition(); + m_group.Render(canvas, session); + } +} + +void SvgView::Canvas_Draw(UI::Xaml::CanvasControl const &sender, UI::Xaml::CanvasDrawEventArgs const &args) { + if (!m_hasRendered) { + m_hasRendered = true; + } + + m_brushes.Clear(); + m_templates.Clear(); + + Render(sender, args.DrawingSession()); +} + +void SvgView::CreateResources(ICanvasResourceCreator const &resourceCreator, UI::CanvasCreateResourcesEventArgs const &args) { + if (m_group) { + m_group.CreateResources(resourceCreator, args); + } +} + +void SvgView::Canvas_CreateResources(UI::Xaml::CanvasControl const &sender, UI::CanvasCreateResourcesEventArgs const &args) { + CreateResources(sender, args); +} + +void SvgView::Canvas_SizeChanged( + IInspectable const & /*sender*/, + Windows::UI::Xaml::SizeChangedEventArgs const & /*args*/) { + // sender.Invalidate(); +} + +void SvgView::Panel_Unloaded(IInspectable const &sender, Windows::UI::Xaml::RoutedEventArgs const & /*args*/) { + if (auto const &svgView{sender.try_as()}) { + svgView.Unload(); + } +} + +void SvgView::Unload() { + m_reactContext = nullptr; + m_templates.Clear(); + m_brushes.Clear(); + + if (m_group) { + m_group.Unload(); + } + + if (m_canvas) { + m_canvas.RemoveFromVisualTree(); + m_canvas = nullptr; + } +} + +void SvgView::InvalidateCanvas() { + if (m_hasRendered) { + m_canvas.Invalidate(); + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/SvgView.h b/windows/RNSVG/SvgView.h new file mode 100644 index 00000000..2c4a49e9 --- /dev/null +++ b/windows/RNSVG/SvgView.h @@ -0,0 +1,95 @@ +#pragma once + +#include "SvgView.g.h" + +namespace winrt::RNSVG::implementation { +struct SvgView : SvgViewT { + public: + SvgView() = default; + + SvgView(Microsoft::ReactNative::IReactContext const &context); + + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl Canvas() { return m_canvas; } + + Windows::UI::Xaml::FrameworkElement SvgParent() { return m_parent; } + void SvgParent(Windows::UI::Xaml::FrameworkElement const &value); + + RNSVG::GroupView Group() { return m_group; } + void Group(RNSVG::GroupView const &value) { m_group = value; } + + Microsoft::Graphics::Canvas::Geometry::CanvasGeometry Geometry() { return m_group ? m_group.Geometry() : nullptr; } + void Geometry(Microsoft::Graphics::Canvas::Geometry::CanvasGeometry /*value*/) { } + + float SvgScale() { return m_scale; } + + Windows::Foundation::Collections::IMap Templates() { + return m_templates; + } + Windows::Foundation::Collections::IMap Brushes() { + return m_brushes; + } + + // IRenderable + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate = true, bool invalidate = true); + void MergeProperties(RNSVG::RenderableView const &other); + void SaveDefinition(); + void Unload(); + void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + void CreateResources( + Microsoft::Graphics::Canvas::ICanvasResourceCreator const &resourceCreator, + Microsoft::Graphics::Canvas::UI::CanvasCreateResourcesEventArgs const &args); + + // Overrides + Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size availableSize); + Windows::Foundation::Size ArrangeOverride(Windows::Foundation::Size finalSize); + + // CanvasControl + void Canvas_Draw( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &sender, + Microsoft::Graphics::Canvas::UI::Xaml::CanvasDrawEventArgs const &args); + void Canvas_CreateResources( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &sender, + Microsoft::Graphics::Canvas::UI::CanvasCreateResourcesEventArgs const &args); + void Canvas_SizeChanged( + Windows::Foundation::IInspectable const &sender, + Windows::UI::Xaml::SizeChangedEventArgs const &args); + + void Panel_Unloaded(Windows::Foundation::IInspectable const &sender, Windows::UI::Xaml::RoutedEventArgs const &args); + + void InvalidateCanvas(); + + private: + bool m_hasRendered{false}; + Microsoft::ReactNative::IReactContext m_reactContext{nullptr}; + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl m_canvas{}; + Windows::UI::Xaml::FrameworkElement m_parent{nullptr}; + RNSVG::GroupView m_group{nullptr}; + hstring m_id{L""}; + float m_scale{0.0f}; + float m_minX{0.0f}; + float m_minY{0.0f}; + float m_vbWidth{0.0f}; + float m_vbHeight{0.0f}; + RNSVG::SVGLength m_bbWidth{}; + RNSVG::SVGLength m_bbHeight{}; + RNSVG::SVGLength m_width{}; + RNSVG::SVGLength m_height{}; + std::string m_align{""}; + RNSVG::MeetOrSlice m_meetOrSlice{RNSVG::MeetOrSlice::Meet}; + + Windows::Foundation::Collections::IMap m_templates{ + winrt::single_threaded_map()}; + Windows::Foundation::Collections::IMap m_brushes{ + winrt::single_threaded_map()}; + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl::Draw_revoker m_canvasDrawRevoker{}; + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl::CreateResources_revoker m_canvasCreateResourcesRevoker{}; + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl::SizeChanged_revoker m_canvasSizeChangedRevoker{}; + Windows::UI::Xaml::FrameworkElement::Unloaded_revoker m_panelUnloadedRevoker{}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct SvgView : SvgViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/SvgViewManager.cpp b/windows/RNSVG/SvgViewManager.cpp new file mode 100644 index 00000000..99a0cd8d --- /dev/null +++ b/windows/RNSVG/SvgViewManager.cpp @@ -0,0 +1,108 @@ +#include "pch.h" +#include "SvgViewManager.h" +#if __has_include("SvgViewManager.g.cpp") +#include "SvgViewManager.g.cpp" +#endif + +#include +#include + +#include "RenderableView.h" +#include "SvgView.h" + +namespace winrt { +using namespace Windows::Foundation::Collections; +using namespace Microsoft::Graphics::Canvas::UI::Xaml; +using namespace Microsoft::ReactNative; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +} // namespace winrt + +namespace winrt::RNSVG::implementation { +// IViewManager +hstring SvgViewManager::Name() { + return L"RNSVGSvgView"; +} + +FrameworkElement SvgViewManager::CreateView() { + return winrt::RNSVG::SvgView(m_reactContext); +} + +// IViewManagerWithContext +IReactContext SvgViewManager::ReactContext() { + return m_reactContext; +} + +void SvgViewManager::ReactContext(IReactContext const &reactContext) { + m_reactContext = reactContext; +} + +// IViewManagerWithNativeProperties +IMapView SvgViewManager::NativeProps() { + auto const &nativeProps{winrt::single_threaded_map()}; + + nativeProps.Insert(L"height", ViewManagerPropertyType::Number); + nativeProps.Insert(L"width", ViewManagerPropertyType::Number); + + // viewBox + nativeProps.Insert(L"minX", ViewManagerPropertyType::Number); + nativeProps.Insert(L"minY", ViewManagerPropertyType::Number); + nativeProps.Insert(L"vbWidth", ViewManagerPropertyType::Number); + nativeProps.Insert(L"vbHeight", ViewManagerPropertyType::Number); + nativeProps.Insert(L"bbWidth", ViewManagerPropertyType::Number); + nativeProps.Insert(L"bbHeight", ViewManagerPropertyType::Number); + + // preserveAspectRatio + nativeProps.Insert(L"align", ViewManagerPropertyType::String); + nativeProps.Insert(L"meetOrSlice", ViewManagerPropertyType::Number); + + return nativeProps.GetView(); +} + +void SvgViewManager::UpdateProperties(FrameworkElement const &view, IJSValueReader const &propertyMapReader) { + if (auto const &svgView{view.try_as()}) { + svgView->UpdateProperties(propertyMapReader); + } +} + +// IViewManagerWithChildren +void SvgViewManager::AddView(FrameworkElement const &parent, UIElement const &child, int64_t /*index*/) { + auto const &svgView{parent.try_as()}; + auto const &group{child.try_as()}; + + if (svgView && group) { + // Every SvgView has exactly one child - a Group that gets + // all of Svg's children piped through. + group.SvgParent(parent); + svgView.Group(group); + } +} + +void SvgViewManager::RemoveAllChildren(FrameworkElement const &parent) { + auto const &svgView{parent.try_as()}; + if (svgView && svgView.Group()) { + svgView.Group().Unload(); + } + svgView.Group(nullptr); +} + +void SvgViewManager::RemoveChildAt(FrameworkElement const &parent, int64_t /*index*/) { + RemoveAllChildren(parent); +} + +void SvgViewManager::ReplaceChild( + FrameworkElement const &parent, + UIElement const &oldChild, + UIElement const &newChild) { + auto const &svgView{parent.try_as()}; + auto const &oldGroup{oldChild.try_as()}; + auto const &newGroup{newChild.try_as()}; + + if (svgView && oldGroup && newGroup) { + newGroup.MergeProperties(oldGroup); + oldGroup.Unload(); + newGroup.SvgParent(parent); + svgView.Group(newGroup); + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/SvgViewManager.h b/windows/RNSVG/SvgViewManager.h new file mode 100644 index 00000000..e948638a --- /dev/null +++ b/windows/RNSVG/SvgViewManager.h @@ -0,0 +1,40 @@ +#pragma once + +#include "SvgViewManager.g.h" + +namespace winrt::RNSVG::implementation { +struct SvgViewManager : SvgViewManagerT { + SvgViewManager() = default; + + // IViewManager + hstring Name(); + Windows::UI::Xaml::FrameworkElement CreateView(); + + // IViewManagerWithReactContext + Microsoft::ReactNative::IReactContext ReactContext(); + void ReactContext(Microsoft::ReactNative::IReactContext const &value); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); + void UpdateProperties( + Windows::UI::Xaml::FrameworkElement const &view, + Microsoft::ReactNative::IJSValueReader const &propertyMapReader); + + // IViewManagerWithChildren + void + AddView(Windows::UI::Xaml::FrameworkElement const &parent, Windows::UI::Xaml::UIElement const &child, int64_t index); + void RemoveAllChildren(Windows::UI::Xaml::FrameworkElement const &parent); + void RemoveChildAt(Windows::UI::Xaml::FrameworkElement const &parent, int64_t index); + void ReplaceChild( + Windows::UI::Xaml::FrameworkElement const &parent, + Windows::UI::Xaml::UIElement const &oldChild, + Windows::UI::Xaml::UIElement const &newChild); + + private: + Microsoft::ReactNative::IReactContext m_reactContext{nullptr}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct SvgViewManager : SvgViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/SymbolView.cpp b/windows/RNSVG/SymbolView.cpp new file mode 100644 index 00000000..6631a739 --- /dev/null +++ b/windows/RNSVG/SymbolView.cpp @@ -0,0 +1,37 @@ +#include "pch.h" +#include "SymbolView.h" +#include "SymbolView.g.cpp" + +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void SymbolView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "vbWidth") { + m_vbWidth = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "vbHeight") { + m_vbHeight = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "minX") { + m_minX = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "minY") { + m_minY = Utils::JSValueAsFloat(propertyValue); + } else if (propertyName == "align") { + m_align = Utils::JSValueAsString(propertyValue); + } else if (propertyName == "meetOrSlice") { + m_meetOrSlice = Utils::GetMeetOrSlice(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/SymbolView.h b/windows/RNSVG/SymbolView.h new file mode 100644 index 00000000..9a37905d --- /dev/null +++ b/windows/RNSVG/SymbolView.h @@ -0,0 +1,35 @@ +#pragma once +#include "SymbolView.g.h" +#include "GroupView.h" + +namespace winrt::RNSVG::implementation { +struct SymbolView : SymbolViewT { + public: + SymbolView() = default; + + float MinX() { return m_minX; } + float MinY() { return m_minY; } + float VbWidth() { return m_vbWidth; } + float VbHeight() { return m_vbHeight; } + hstring Align() { return to_hstring(m_align); } + RNSVG::MeetOrSlice MeetOrSlice() { return m_meetOrSlice; } + + // RenderableView + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &/*canvas*/, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &/*session*/){}; + + private: + float m_minX{0.0f}; + float m_minY{0.0f}; + float m_vbWidth{0.0f}; + float m_vbHeight{0.0f}; + std::string m_align{""}; + RNSVG::MeetOrSlice m_meetOrSlice{RNSVG::MeetOrSlice::Meet}; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct SymbolView : SymbolViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/SymbolViewManager.cpp b/windows/RNSVG/SymbolViewManager.cpp new file mode 100644 index 00000000..84fb600d --- /dev/null +++ b/windows/RNSVG/SymbolViewManager.cpp @@ -0,0 +1,32 @@ +#include "pch.h" +#include "SymbolViewManager.h" +#include "SymbolViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation +{ +SymbolViewManager::SymbolViewManager() { + m_class = RNSVG::SVGClass::RNSVGSymbol; + m_name = L"RNSVGSymbol"; +} + +IMapView SymbolViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"minX", ViewManagerPropertyType::Number); + nativeProps.Insert(L"minY", ViewManagerPropertyType::Number); + nativeProps.Insert(L"vbWidth", ViewManagerPropertyType::Number); + nativeProps.Insert(L"vbHeight", ViewManagerPropertyType::Number); + nativeProps.Insert(L"align", ViewManagerPropertyType::String); + nativeProps.Insert(L"meetOrSlice", ViewManagerPropertyType::Number); + + return nativeProps.GetView(); +} +} diff --git a/windows/RNSVG/SymbolViewManager.h b/windows/RNSVG/SymbolViewManager.h new file mode 100644 index 00000000..5848e04d --- /dev/null +++ b/windows/RNSVG/SymbolViewManager.h @@ -0,0 +1,16 @@ +#pragma once +#include "SymbolViewManager.g.h" +#include "GroupViewManager.h" + +namespace winrt::RNSVG::implementation { +struct SymbolViewManager : SymbolViewManagerT { + SymbolViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct SymbolViewManager : SymbolViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/TSpanView.cpp b/windows/RNSVG/TSpanView.cpp new file mode 100644 index 00000000..5414a0fc --- /dev/null +++ b/windows/RNSVG/TSpanView.cpp @@ -0,0 +1,52 @@ +#include "pch.h" +#include "TSpanView.h" +#include "TSpanView.g.cpp" + +#include "Utils.h" +#include + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void TSpanView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "content") { + m_content = propertyValue.AsString(); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void TSpanView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) { + auto const &resourceCreator{canvas.try_as()}; + Microsoft::Graphics::Canvas::Text::CanvasTextFormat const& textFormat{}; + textFormat.FontSize(FontSize()); + textFormat.FontFamily(FontFamily()); + textFormat.FontWeight(Utils::FontWeightFrom(FontWeight(), SvgParent())); + + Geometry(Geometry::CanvasGeometry::CreateText({resourceCreator, to_hstring(m_content), textFormat, canvas.Size().Width, canvas.Size().Height})); +} + +void TSpanView::Render(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + auto const &transform{session.Transform()}; + bool translateXY{X().Size() > 0 || Y().Size() > 0}; + + if (translateXY) { + float x{X().Size() > 0 ? X().GetAt(0).Value() : 0}; + float y{Y().Size() > 0 ? Y().GetAt(0).Value() : 0}; + session.Transform(transform * Numerics::make_float3x2_translation(x, y)); + } + __super::Render(canvas, session); + if (translateXY) { + session.Transform(transform); + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/TSpanView.h b/windows/RNSVG/TSpanView.h new file mode 100644 index 00000000..a2ef4689 --- /dev/null +++ b/windows/RNSVG/TSpanView.h @@ -0,0 +1,23 @@ +#pragma once +#include "TSpanView.g.h" +#include "TextView.h" + +namespace winrt::RNSVG::implementation { +struct TSpanView : TSpanViewT { +public: + TSpanView() = default; + + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void CreateGeometry(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas); + virtual void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + + private: + std::string m_content; +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct TSpanView : TSpanViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/TSpanViewManager.cpp b/windows/RNSVG/TSpanViewManager.cpp new file mode 100644 index 00000000..5d55a7cf --- /dev/null +++ b/windows/RNSVG/TSpanViewManager.cpp @@ -0,0 +1,26 @@ +#include "pch.h" +#include "TSpanViewManager.h" +#include "TSpanViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +TSpanViewManager::TSpanViewManager() { + m_class = RNSVG::SVGClass::RNSVGTSpan; + m_name = L"RNSVGTSpan"; +} + +Windows::Foundation::Collections::IMapView TSpanViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"content", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} diff --git a/windows/RNSVG/TSpanViewManager.h b/windows/RNSVG/TSpanViewManager.h new file mode 100644 index 00000000..6ff3aad1 --- /dev/null +++ b/windows/RNSVG/TSpanViewManager.h @@ -0,0 +1,15 @@ +#pragma once +#include "TSpanViewManager.g.h" +#include "TextViewManager.h" + +namespace winrt::RNSVG::implementation { +struct TSpanViewManager : TSpanViewManagerT { + TSpanViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct TSpanViewManager : TSpanViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/TextView.cpp b/windows/RNSVG/TextView.cpp new file mode 100644 index 00000000..40ceaa26 --- /dev/null +++ b/windows/RNSVG/TextView.cpp @@ -0,0 +1,61 @@ +#include "pch.h" +#include "TextView.h" +#include "TextView.g.cpp" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void TextView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "x") { + m_x.Clear(); + for (auto const &item : propertyValue.AsArray()) { + m_x.Append(SVGLength::From(item)); + } + } else if (propertyName == "y") { + m_y.Clear(); + for (auto const &item : propertyValue.AsArray()) { + m_y.Append(SVGLength::From(item)); + } + } else if (propertyName == "dx") { + m_dx.Clear(); + for (auto const &item : propertyValue.AsArray()) { + m_dx.Append(SVGLength::From(item)); + } + } else if (propertyName == "dy") { + m_dy.Clear(); + for (auto const &item : propertyValue.AsArray()) { + m_dy.Append(SVGLength::From(item)); + } + } else if (propertyName == "rotate") { + m_rotate.Clear(); + for (auto const &item : propertyValue.AsArray()) { + m_rotate.Append(SVGLength::From(item)); + } + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void TextView::RenderGroup(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + auto const &transform{session.Transform()}; + bool translateXY{X().Size() > 0 || Y().Size() > 0}; + if (translateXY) { + float x{X().Size() > 0 ? X().GetAt(0).Value() : 0}; + float y{Y().Size() > 0 ? Y().GetAt(0).Value() : 0}; + session.Transform(transform * Numerics::make_float3x2_translation(x, y)); + } + __super::RenderGroup(canvas, session); + if (translateXY) { + session.Transform(transform); + } +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/TextView.h b/windows/RNSVG/TextView.h new file mode 100644 index 00000000..2ca5753c --- /dev/null +++ b/windows/RNSVG/TextView.h @@ -0,0 +1,31 @@ +#pragma once +#include "TextView.g.h" +#include "GroupView.h" + +namespace winrt::RNSVG::implementation { +struct TextView : TextViewT { + public: + TextView() = default; + + Windows::Foundation::Collections::IVector X() { return m_x; } + Windows::Foundation::Collections::IVector Y() { return m_y; } + Windows::Foundation::Collections::IVector DX() { return m_dx; } + Windows::Foundation::Collections::IVector DY() { return m_dy; } + Windows::Foundation::Collections::IVector Rotate() { return m_rotate; } + + virtual void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + virtual void RenderGroup( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + + private: + Windows::Foundation::Collections::IVector m_x{winrt::single_threaded_vector()}; + Windows::Foundation::Collections::IVector m_y{winrt::single_threaded_vector()}; + Windows::Foundation::Collections::IVector m_dx{winrt::single_threaded_vector()}; + Windows::Foundation::Collections::IVector m_dy{winrt::single_threaded_vector()}; + Windows::Foundation::Collections::IVector m_rotate{winrt::single_threaded_vector()}; +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct TextView : TextViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/TextViewManager.cpp b/windows/RNSVG/TextViewManager.cpp new file mode 100644 index 00000000..86268cf9 --- /dev/null +++ b/windows/RNSVG/TextViewManager.cpp @@ -0,0 +1,30 @@ +#include "pch.h" +#include "TextViewManager.h" +#include "TextViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +TextViewManager::TextViewManager() { + m_class = RNSVG::SVGClass::RNSVGText; + m_name = L"RNSVGText"; +} + +Windows::Foundation::Collections::IMapView TextViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"x", ViewManagerPropertyType::Array); + nativeProps.Insert(L"y", ViewManagerPropertyType::Array); + nativeProps.Insert(L"dx", ViewManagerPropertyType::Array); + nativeProps.Insert(L"dy", ViewManagerPropertyType::Array); + nativeProps.Insert(L"rotate", ViewManagerPropertyType::Array); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/TextViewManager.h b/windows/RNSVG/TextViewManager.h new file mode 100644 index 00000000..79f307a7 --- /dev/null +++ b/windows/RNSVG/TextViewManager.h @@ -0,0 +1,15 @@ +#pragma once +#include "TextViewManager.g.h" +#include "GroupViewManager.h" + +namespace winrt::RNSVG::implementation { +struct TextViewManager : TextViewManagerT { + TextViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation +namespace winrt::RNSVG::factory_implementation { +struct TextViewManager : TextViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/Types.idl b/windows/RNSVG/Types.idl new file mode 100644 index 00000000..4d3f1b87 --- /dev/null +++ b/windows/RNSVG/Types.idl @@ -0,0 +1,79 @@ +namespace RNSVG { + enum SVGClass { + RNSVGGroup, + RNSVGPath, + RNSVGText, + RNSVGTSpan, + RNSVGTextPath, + RNSVGImage, + RNSVGCircle, + RNSVGEllipse, + RNSVGLine, + RNSVGRect, + RNSVGClipPath, + RNSVGDefs, + RNSVGUse, + RNSVGSymbol, + RNSVGLinearGradient, + RNSVGRadialGradient, + RNSVGPattern, + RNSVGMask, + RNSVGMarker, + RNSVGForeignObject, + Unknown, + }; + + enum MeetOrSlice { + Meet, + Slice, + None, + }; + + enum BaseProp { + Matrix, + Fill, + FillOpacity, + FillRule, + Stroke, + StrokeOpacity, + StrokeWidth, + StrokeMiterLimit, + StrokeDashOffset, + StrokeDashArray, + StrokeLineCap, + StrokeLineJoin, + Unknown, + }; + + enum FontProp { + FontSize, + FontWeight, + FontFamily, + Unknown, + }; + + enum LengthType + { + Unknown, + Number, + Percentage, + EMS, + EXS, + Pixel, + Centimeter, + Millimeter, + Inch, + Point, + Pica, + }; + + [default_interface] + runtimeclass SVGLength { + SVGLength(); + SVGLength(Single param); + SVGLength(Single param, LengthType type); + + Single Value{ get; }; + LengthType Unit{ get; }; + }; +} diff --git a/windows/RNSVG/UseView.cpp b/windows/RNSVG/UseView.cpp new file mode 100644 index 00000000..c7ce866d --- /dev/null +++ b/windows/RNSVG/UseView.cpp @@ -0,0 +1,105 @@ +#include "pch.h" +#include "UseView.h" +#include "UseView.g.cpp" + +#include "Utils.h" + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +void UseView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) { + const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)}; + + for (auto const &pair : propertyMap) { + auto const &propertyName{pair.first}; + auto const &propertyValue{pair.second}; + + if (propertyName == "href") { + m_href = to_hstring(Utils::JSValueAsString(propertyValue)); + } else if (propertyName == "x") { + m_x = SVGLength::From(propertyValue); + } else if (propertyName == "y") { + m_y = SVGLength::From(propertyValue); + } else if (propertyName == "width") { + m_width = SVGLength::From(propertyValue); + } else if (propertyName == "height") { + m_height = SVGLength::From(propertyValue); + } + } + + __super::UpdateProperties(reader, forceUpdate, invalidate); +} + +void UseView::Render(UI::Xaml::CanvasControl const &canvas, CanvasDrawingSession const &session) { + if (auto const &view{GetRenderableTemplate()}) { + auto const &originalTransform{session.Transform()}; + auto transform{Numerics::make_float3x2_scale(1)}; + + // Figure out any necessary transforms + if (auto const &symbol{view.try_as()}) { + if (symbol.Align() != L"") { + if (auto const &root{SvgRoot()}) { + Rect vbRect{ + symbol.MinX() * root.SvgScale(), + symbol.MinY() * root.SvgScale(), + (symbol.MinX() + symbol.VbWidth()) * root.SvgScale(), + (symbol.MinY() + symbol.VbHeight()) * root.SvgScale()}; + + float elX{Utils::GetAbsoluteLength(m_x, canvas.Size().Width)}; + float elY{Utils::GetAbsoluteLength(m_y, canvas.Size().Height)}; + float elWidth{Utils::GetAbsoluteLength(m_width, canvas.Size().Width)}; + float elHeight{Utils::GetAbsoluteLength(m_height, canvas.Size().Height)}; + Rect elRect{elX, elY, elWidth, elHeight}; + + transform = Utils::GetViewBoxTransform(vbRect, elRect, to_string(symbol.Align()), symbol.MeetOrSlice()); + } + } + } else { + float x{Utils::GetAbsoluteLength(m_x, canvas.Size().Width)}; + float y{Utils::GetAbsoluteLength(m_y, canvas.Size().Height)}; + transform = Numerics::make_float3x2_translation({x, y}); + } + + // Combine new transform with existing one if it's set + if (m_propSetMap[RNSVG::BaseProp::Matrix]) { + transform = transform * SvgTransform(); + } + + session.Transform(transform); + + // Propagate props to template + view.MergeProperties(*this); + + // Set opacity and render + if (auto const &opacityLayer{session.CreateLayer(m_opacity)}) { + if (auto const &symbol{view.try_as()}) { + symbol.RenderGroup(canvas, session); + } else { + view.Render(canvas, session); + } + opacityLayer.Close(); + } + + // Restore original template props + if (auto const &parent{view.SvgParent().try_as()}) { + view.MergeProperties(parent); + } + + // Restore session transform + session.Transform(originalTransform); + + } else { + throw hresult_not_implemented(L"'Use' element expected a pre-defined svg template as 'href' prop. Template named: " + m_href + L" is not defined"); + } +} + +RNSVG::IRenderable UseView::GetRenderableTemplate() { + if (auto const &root{SvgRoot()}) { + return root.Templates().TryLookup(m_href); + } + + return nullptr; +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/UseView.h b/windows/RNSVG/UseView.h new file mode 100644 index 00000000..052c3a4d --- /dev/null +++ b/windows/RNSVG/UseView.h @@ -0,0 +1,28 @@ +#pragma once +#include "UseView.g.h" +#include "RenderableView.h" + +namespace winrt::RNSVG::implementation { +struct UseView : UseViewT { + public: + UseView() = default; + + void UpdateProperties(Microsoft::ReactNative::IJSValueReader const &reader, bool forceUpdate, bool invalidate); + void Render( + Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const &canvas, + Microsoft::Graphics::Canvas::CanvasDrawingSession const &session); + + private: + hstring m_href{L""}; + RNSVG::SVGLength m_x{}; + RNSVG::SVGLength m_y{}; + RNSVG::SVGLength m_width{}; + RNSVG::SVGLength m_height{}; + + RNSVG::IRenderable GetRenderableTemplate(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct UseView : UseViewT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/UseViewManager.cpp b/windows/RNSVG/UseViewManager.cpp new file mode 100644 index 00000000..3f110855 --- /dev/null +++ b/windows/RNSVG/UseViewManager.cpp @@ -0,0 +1,30 @@ +#include "pch.h" +#include "UseViewManager.h" +#include "UseViewManager.g.cpp" + +using namespace winrt; +using namespace Microsoft::ReactNative; + +namespace winrt::RNSVG::implementation { +UseViewManager::UseViewManager() { + m_class = RNSVG::SVGClass::RNSVGUse; + m_name = L"RNSVGUse"; +} + +IMapView UseViewManager::NativeProps() { + auto const &parentProps{__super::NativeProps()}; + auto const &nativeProps{winrt::single_threaded_map()}; + + for (auto const &prop : parentProps) { + nativeProps.Insert(prop.Key(), prop.Value()); + } + + nativeProps.Insert(L"href", ViewManagerPropertyType::String); + nativeProps.Insert(L"x", ViewManagerPropertyType::String); + nativeProps.Insert(L"y", ViewManagerPropertyType::String); + nativeProps.Insert(L"width", ViewManagerPropertyType::String); + nativeProps.Insert(L"height", ViewManagerPropertyType::String); + + return nativeProps.GetView(); +} +} // namespace winrt::RNSVG::implementation diff --git a/windows/RNSVG/UseViewManager.h b/windows/RNSVG/UseViewManager.h new file mode 100644 index 00000000..ed23e231 --- /dev/null +++ b/windows/RNSVG/UseViewManager.h @@ -0,0 +1,16 @@ +#pragma once +#include "UseViewManager.g.h" +#include "RenderableViewManager.h" + +namespace winrt::RNSVG::implementation { +struct UseViewManager : UseViewManagerT { + UseViewManager(); + + // IViewManagerWithNativeProperties + Windows::Foundation::Collections::IMapView NativeProps(); +}; +} // namespace winrt::RNSVG::implementation + +namespace winrt::RNSVG::factory_implementation { +struct UseViewManager : UseViewManagerT {}; +} // namespace winrt::RNSVG::factory_implementation diff --git a/windows/RNSVG/Utils.h b/windows/RNSVG/Utils.h new file mode 100644 index 00000000..beb40ec7 --- /dev/null +++ b/windows/RNSVG/Utils.h @@ -0,0 +1,341 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include +#include "JSValueReader.h" + +#define _USE_MATH_DEFINES +#include + +using namespace winrt; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::ReactNative; +using namespace Windows::UI; +using namespace Windows::UI::Text; + +namespace winrt::RNSVG { +struct Utils { + public: + static std::vector GetAdjustedStrokeArray(IVector const &value, float strokeWidth, float canvasDiagonal) { + std::vector result; + + for (auto const &item : value) { + float absValue{GetAbsoluteLength(item, canvasDiagonal)}; + + // Win2D sets the length of each dash as the product of the element value in array and stroke width, + // we divide each value in the dashArray by StrokeWidth to account for this. + // http://microsoft.github.io/Win2D/WinUI2/html/P_Microsoft_Graphics_Canvas_Geometry_CanvasStrokeStyle_CustomDashStyle.htm + result.push_back(absValue / (strokeWidth == 0.0f ? 1.0f : strokeWidth)); + } + + return std::move(result); + } + + static float GetCanvasDiagonal(Windows::Foundation::Size const &size) { + float powX{std::powf(size.Width, 2)}; + float powY{std::powf(size.Height, 2)}; + + return std::sqrtf(powX + powY) * static_cast(M_SQRT1_2); + } + + static float GetAbsoluteLength(SVGLength const &length, float relativeTo) { + auto value{length.Value()}; + + // 1in = 2.54cm = 96px + auto inch{96.0f}; + auto cm{inch / 2.54f}; + + switch (length.Unit()) { + case RNSVG::LengthType::Percentage: + return value / 100.0f * relativeTo; + case RNSVG::LengthType::Centimeter: + // 1cm = 96px/2.54 + return value * cm; + case RNSVG::LengthType::Millimeter: + // 1mm = 1/10th of 1cm + return value * cm / 10.0f; + case RNSVG::LengthType::Inch: + // 1in = 2.54cm = 96px + return value * inch; + case RNSVG::LengthType::Point: + // 1pt = 1/72th of 1in + return value * inch / 72.0f; + case RNSVG::LengthType::Pica: + // 1pc = 1/6th of 1in + return value * inch / 6.0f; + case RNSVG::LengthType::Pixel: + default: + return value; + } + } + + static Numerics::float3x2 GetRotationMatrix(float degrees) { + // convert to radians + auto radians{degrees * static_cast(M_PI) / 100.0f}; + return Numerics::make_float3x2_rotation(radians); + } + + static FontWeight FontWeightFrom(hstring const& weight, Xaml::FrameworkElement const& parent) { + if (weight == L"normal") { + return FontWeights::Normal(); + } else if (weight == L"bold") { + return FontWeights::Bold(); + } else if (weight == L"bolder" || weight == L"lighter" || weight == L"auto") { + auto const &groupView{parent.try_as()}; + FontWeight parentWeight{ + groupView ? FontWeightFrom(groupView.FontWeight(), groupView.SvgParent()) : FontWeights::Normal()}; + + if (weight == L"bolder") { + return Bolder(parentWeight.Weight); + } else if (weight == L"lighter") { + return Lighter(parentWeight.Weight); + } else if (weight == L"auto") { + return parentWeight; + } + } + + return GetClosestFontWeight(std::stof(weight.c_str(), nullptr)); + } + + static FontWeight GetClosestFontWeight(float weight) { + if (weight > 325 && weight < 375) { + return FontWeights::SemiLight(); + } else if (weight > 925) { + return FontWeights::ExtraBlack(); + } else { + switch (static_cast(std::round(weight / 100.0f))) { + case 1: + return FontWeights::Thin(); + case 2: + return FontWeights::ExtraLight(); + case 3: + return FontWeights::Light(); + case 4: + return FontWeights::Normal(); + case 5: + return FontWeights::Medium(); + case 6: + return FontWeights::SemiBold(); + case 7: + return FontWeights::Bold(); + case 8: + return FontWeights::ExtraBold(); + case 9: + default: + return FontWeights::ExtraBlack(); + } + } + } + + static FontWeight Bolder(uint16_t weight) { + if (weight < 350) { + return FontWeights::Normal(); + } else if (weight < 550) { + return FontWeights::Bold(); + } else if (weight < 900) { + return FontWeights::Black(); + } else { + return FontWeights::ExtraBlack(); + } + } + + static FontWeight Lighter(uint16_t weight) { + if (weight < 550) { + return FontWeights::Thin(); + } else if (weight < 750) { + return FontWeights::Normal(); + } else { + return FontWeights::Bold(); + } + } + + static Numerics::float3x2 GetViewBoxTransform(Rect vbRect, Rect elRect, std::string align, RNSVG::MeetOrSlice meetOrSlice) { + // based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform + + // Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute + // respectively. + float vbX = vbRect.X; + float vbY = vbRect.Y; + float vbWidth = vbRect.Width; + float vbHeight = vbRect.Height; + + // Let e-x, e-y, e-width, e-height be the position and size of the element respectively. + float eX = elRect.X; + float eY = elRect.Y; + float eWidth = elRect.Width; + float eHeight = elRect.Height; + + // Initialize scale-x to e-width/vb-width. + float scaleX = eWidth / vbWidth; + + // Initialize scale-y to e-height/vb-height. + float scaleY = eHeight / vbHeight; + + // If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller. + // Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the + // larger. + if (align != "none" && meetOrSlice == RNSVG::MeetOrSlice::Meet) { + scaleX = scaleY = std::min(scaleX, scaleY); + } else if (align != "none" && meetOrSlice == RNSVG::MeetOrSlice::Slice) { + scaleX = scaleY = std::max(scaleX, scaleY); + } + + // Initialize translate-x to e-x - (vb-x * scale-x). + float translateX = eX - (vbX * scaleX); + + // Initialize translate-y to e-y - (vb-y * scale-y). + float translateY = eY - (vbY * scaleY); + + // If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x. + if (align.find("xMid") != std::string::npos) { + translateX += (eWidth - vbWidth * scaleX) / 2.0f; + } + + // If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x. + if (align.find("xMax") != std::string::npos) { + translateX += (eWidth - vbWidth * scaleX); + } + + // If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y. + if (align.find("YMid") != std::string::npos) { + translateY += (eHeight - vbHeight * scaleY) / 2.0f; + } + + // If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y. + if (align.find("YMax") != std::string::npos) { + translateY += (eHeight - vbHeight * scaleY); + } + + // The transform applied to content contained by the element is given by + // translate(translate-x, translate-y) scale(scale-x, scale-y). + auto const &translate{Numerics::make_float3x2_translation(translateX, translateY)}; + auto const &scale{Numerics::make_float3x2_scale(scaleX, scaleY)}; + + return scale * translate; + } + + static RNSVG::MeetOrSlice GetMeetOrSlice(JSValue const &value) { + if (value.IsNull()) { + return RNSVG::MeetOrSlice::Meet; + } + + switch (value.AsInt8()) { + case 2: + return RNSVG::MeetOrSlice::None; + case 1: + return RNSVG::MeetOrSlice::Slice; + case 0: + default: + return RNSVG::MeetOrSlice::Meet; + } + } + + static std::string JSValueAsBrushUnits(JSValue const &value, std::string defaultValue = "objectBoundingBox") { + if (value.IsNull()) { + return defaultValue; + } else { + switch (value.AsInt32()) { + case 1: + return "userSpaceOnUse"; + case 0: + default: + return "objectBoundingBox"; + } + } + } + + static float JSValueAsFloat(JSValue const &value, float defaultValue = 0.0f) { + if (value.IsNull()) { + return defaultValue; + } else { + return value.AsSingle(); + } + } + + static std::string JSValueAsString(JSValue const &value, std::string defaultValue = "") { + if (value.IsNull()) { + return defaultValue; + } else { + return value.AsString(); + } + } + + static Color JSValueAsColor(JSValue const &value, Color defaultValue = Colors::Transparent()) { + if (value.IsNull()) { + return defaultValue; + } else if (auto const &brush{value.To()}) { + if (auto const &scb{brush.try_as()}) { + return scb.Color(); + } + } + + return defaultValue; + } + + static SVGLength JSValueAsSVGLength(JSValue const &value, SVGLength const &defaultValue = {}) { + if (value.IsNull()) { + return defaultValue; + } else { + return RNSVG::implementation::SVGLength::From(value); + } + } + + static Numerics::float3x2 JSValueAsTransform(JSValue const& value, Numerics::float3x2 defaultValue = {}) { + if (value.IsNull()) { + return defaultValue; + } else { + auto const &matrix{value.AsArray()}; + + return Numerics::float3x2( + matrix.at(0).AsSingle(), + matrix.at(1).AsSingle(), + matrix.at(2).AsSingle(), + matrix.at(3).AsSingle(), + matrix.at(4).AsSingle(), + matrix.at(5).AsSingle()); + } + } + + static std::vector JSValueAsStops(JSValue const& value) { + if (value.IsNull()) { + return {}; + } + + auto const &stops{value.AsArray()}; + std::vector canvasStops{}; + + for (size_t i = 0; i < stops.size(); ++i) { + Brushes::CanvasGradientStop stop{}; + stop.Position = Utils::JSValueAsFloat(stops.at(i)); + stop.Color = Utils::JSValueAsColor(stops.at(++i)); + canvasStops.push_back(stop); + } + + return canvasStops; + } + + static Brushes::ICanvasBrush GetCanvasBrush( + hstring const &brushId, + Color color, + RNSVG::SvgView const &root, + Geometry::CanvasGeometry const &geometry, + ICanvasResourceCreator const &resourceCreator) { + Brushes::ICanvasBrush brush{nullptr}; + if (root && brushId != L"") { + if (auto const &brushView{root.Brushes().TryLookup(brushId)}) { + brushView.SetBounds(geometry.ComputeBounds()); + brush = brushView.Brush(); + } + } + + if (!brush) { + brush = Brushes::CanvasSolidColorBrush(resourceCreator, color); + } + + return brush; + } +}; +} // namespace winrt::RNSVG diff --git a/windows/RNSVG/ViewManagers.idl b/windows/RNSVG/ViewManagers.idl new file mode 100644 index 00000000..bfd69648 --- /dev/null +++ b/windows/RNSVG/ViewManagers.idl @@ -0,0 +1,115 @@ +import "Types.idl"; + +namespace RNSVG +{ + [default_interface] + runtimeclass SvgViewManager + : Microsoft.ReactNative.IViewManager + , Microsoft.ReactNative.IViewManagerWithReactContext + , Microsoft.ReactNative.IViewManagerWithNativeProperties + , Microsoft.ReactNative.IViewManagerWithChildren + { + SvgViewManager(); + }; + + [default_interface] + unsealed runtimeclass RenderableViewManager + : Microsoft.ReactNative.IViewManager + , Microsoft.ReactNative.IViewManagerWithReactContext + , Microsoft.ReactNative.IViewManagerWithNativeProperties + { + RenderableViewManager(); + }; + + [default_interface] + runtimeclass RectViewManager : RenderableViewManager + { + RectViewManager(); + }; + + [default_interface] + runtimeclass CircleViewManager : RenderableViewManager + { + CircleViewManager(); + }; + + [default_interface] + runtimeclass EllipseViewManager : RenderableViewManager + { + EllipseViewManager(); + }; + + [default_interface] + runtimeclass LineViewManager : RenderableViewManager + { + LineViewManager(); + }; + + [default_interface] + runtimeclass PathViewManager : RenderableViewManager + { + PathViewManager(); + }; + + [default_interface] + runtimeclass UseViewManager : RenderableViewManager + { + UseViewManager(); + }; + + [default_interface] + runtimeclass ImageViewManager : RenderableViewManager + { + ImageViewManager(); + }; + + [default_interface] + unsealed runtimeclass GroupViewManager + : RenderableViewManager + , Microsoft.ReactNative.IViewManagerWithChildren + { + GroupViewManager(); + }; + + [default_interface] + runtimeclass DefsViewManager : GroupViewManager + { + DefsViewManager(); + }; + + [default_interface] + runtimeclass LinearGradientViewManager : GroupViewManager + { + LinearGradientViewManager(); + }; + + [default_interface] + runtimeclass RadialGradientViewManager : GroupViewManager + { + RadialGradientViewManager(); + }; + + [default_interface] + runtimeclass PatternViewManager : GroupViewManager + { + PatternViewManager(); + }; + + [default_interface] + runtimeclass SymbolViewManager : GroupViewManager + { + SymbolViewManager(); + }; + + [default_interface] + unsealed runtimeclass TextViewManager : GroupViewManager + { + TextViewManager(); + }; + + [default_interface] + runtimeclass TSpanViewManager : TextViewManager + { + TSpanViewManager(); + }; +} diff --git a/windows/RNSVG/Views.idl b/windows/RNSVG/Views.idl new file mode 100644 index 00000000..ac76c5ba --- /dev/null +++ b/windows/RNSVG/Views.idl @@ -0,0 +1,181 @@ +import "Types.idl"; + +namespace RNSVG +{ + interface IRenderable + { + Windows.UI.Xaml.FrameworkElement SvgParent; + Microsoft.Graphics.Canvas.Geometry.CanvasGeometry Geometry; + + void CreateResources( + Microsoft.Graphics.Canvas.ICanvasResourceCreator resourceCreator, + Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args); + void Render( + Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl canvas, + Microsoft.Graphics.Canvas.CanvasDrawingSession session); + void UpdateProperties(Microsoft.ReactNative.IJSValueReader reader, Boolean forceUpdate, Boolean invalidate); + void MergeProperties(RenderableView other); + void SaveDefinition(); + void Unload(); + }; + + [default_interface] + runtimeclass SvgView : Windows.UI.Xaml.Controls.Panel, IRenderable + { + SvgView(Microsoft.ReactNative.IReactContext context); + + Single SvgScale{ get; }; + GroupView Group; + Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl Canvas{ get; }; + Windows.Foundation.Collections.IMap Templates{ get; }; + Windows.Foundation.Collections.IMap Brushes{ get; }; + + void InvalidateCanvas(); + }; + + [default_interface] + unsealed runtimeclass RenderableView : Windows.UI.Xaml.FrameworkElement, IRenderable + { + RenderableView(Microsoft.ReactNative.IReactContext context); + SvgView SvgRoot{ get; }; + + String Id{ get; }; + Windows.Foundation.Numerics.Matrix3x2 SvgTransform{ get; }; + Windows.UI.Color Fill{ get; }; + Single FillOpacity{ get; }; + String FillBrushId{ get; }; + Windows.UI.Color Stroke{ get; }; + Single StrokeOpacity{ get; }; + String StrokeBrushId{ get; }; + SVGLength StrokeWidth{ get; }; + Single StrokeMiterLimit{ get; }; + Single StrokeDashOffset{ get; }; + Windows.Foundation.Collections.IVector StrokeDashArray{ get; }; + Microsoft.Graphics.Canvas.Geometry.CanvasCapStyle StrokeLineCap{ get; }; + Microsoft.Graphics.Canvas.Geometry.CanvasLineJoin StrokeLineJoin{ get; }; + Microsoft.Graphics.Canvas.Geometry.CanvasFilledRegionDetermination FillRule{ get; }; + + void CreateGeometry(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl canvas); + }; + + [default_interface] + runtimeclass RectView : RenderableView + { + RectView(); + }; + + [default_interface] + runtimeclass CircleView : RenderableView + { + CircleView(); + }; + + [default_interface] + runtimeclass EllipseView : RenderableView + { + EllipseView(); + }; + + [default_interface] + runtimeclass LineView : RenderableView + { + LineView(); + }; + + [default_interface] + runtimeclass PathView : RenderableView + { + PathView(); + }; + + [default_interface] + runtimeclass UseView : RenderableView + { + UseView(); + }; + + [default_interface] + runtimeclass ImageView : RenderableView + { + ImageView(); + }; + + [default_interface] + unsealed runtimeclass GroupView : RenderableView + { + GroupView(Microsoft.ReactNative.IReactContext context); + Windows.Foundation.Collections.IVector Children { get; }; + + Single FontSize; + String FontFamily; + String FontWeight; + + void RenderGroup( + Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl canvas, + Microsoft.Graphics.Canvas.CanvasDrawingSession session); + }; + + [default_interface] + unsealed runtimeclass TextView : GroupView + { + TextView(); + Windows.Foundation.Collections.IVector X{ get; }; + Windows.Foundation.Collections.IVector Y{ get; }; + Windows.Foundation.Collections.IVector DX{ get; }; + Windows.Foundation.Collections.IVector DY{ get; }; + }; + + [default_interface] + runtimeclass TSpanView : TextView + { + TSpanView(); + + Windows.Foundation.Collections.IVector Rotate { get; }; + }; + + [default_interface] + runtimeclass DefsView : GroupView + { + DefsView(); + }; + + [default_interface] + runtimeclass SymbolView : GroupView + { + SymbolView(); + Single MinX{ get; }; + Single MinY{ get; }; + Single VbWidth{ get; }; + Single VbHeight{ get; }; + String Align{ get; }; + MeetOrSlice MeetOrSlice{ get; }; + }; + + [default_interface] + unsealed runtimeclass BrushView : GroupView + { + BrushView(); + + Microsoft.Graphics.Canvas.Brushes.ICanvasBrush Brush{ get; }; + void CreateBrush(); + void SetBounds(Windows.Foundation.Rect rect); + }; + + [default_interface] + runtimeclass LinearGradientView : BrushView + { + LinearGradientView(); + }; + + [default_interface] + runtimeclass RadialGradientView : BrushView + { + RadialGradientView(); + }; + + [default_interface] + runtimeclass PatternView : BrushView + { + PatternView(); + }; +} diff --git a/windows/RNSVG/packages.config b/windows/RNSVG/packages.config new file mode 100644 index 00000000..e743ed29 --- /dev/null +++ b/windows/RNSVG/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/windows/RNSVG/pch.cpp b/windows/RNSVG/pch.cpp new file mode 100644 index 00000000..bcb5590b --- /dev/null +++ b/windows/RNSVG/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/windows/RNSVG/pch.h b/windows/RNSVG/pch.h new file mode 100644 index 00000000..6c6e58df --- /dev/null +++ b/windows/RNSVG/pch.h @@ -0,0 +1,31 @@ +#pragma once + +#define NOMINMAX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +using namespace winrt::Windows::Foundation;