diff --git a/README.md b/README.md index 308849f2..6ae32ab4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ So you should install react-native-pdf and rn-fetch-blob | progress-bar-android | | | | 1.0.3+ | | progress-view | | | | 1.0.3+ | +Currently, Windows support is partial. The rn-fetch-blob module is not yet available on Windows, only loading bundled PDFs is supported. + ### Installation ```bash @@ -86,6 +88,31 @@ react-native link react-native-pdf +### Windows installation +
+ Windows details + +- Open your solution in Visual Studio 2019 (eg. `windows\yourapp.sln`) +- Right-click Solution icon in Solution Explorer > Add > Existing Project... +- Add `node_modules\@react-native-community\progress-view\windows\progress-view\progress-view.vcxproj` +- If running RNW 0.62: add `node_modules\react-native-pdf\windows\RCTPdf\RCTPdf.vcxproj` +- Right-click main application project > Add > Reference... + - Select `progress-view` and in Solution Projects + - If running 0.62, also select `RCTPdf` +- In app `pch.h` add `#include "winrt/progress-view.h"` and `#include "winrt/RCTPdf.h"` +- In `App.cpp` add `PackageProviders().Append(winrt::progress-view::ReactPackageProvider());` before `InitializeComponent();` +- If running RNW 0.62, also add `PackageProviders().Append(winrt::RCTPdf::ReactPackageProvider());` + +#### Bundling PDFs with the app +To add a `test.pdf` like in the example add: +``` + + true + +``` +in the app `.vcxproj` file, before ``. +
+ ### FAQ
FAQ details @@ -270,55 +297,56 @@ const styles = StyleSheet.create({ ### Configuration -| Property | Type | Default | Description | iOS | Android | FirstRelease | -| ------------- |:-------------:|:----------------:| ------------------- | ------| ------- | ------------ | -| source | object | not null | PDF source like {uri:xxx, cache:false}. see the following for detail.| ✔ | ✔ | <3.0 | -| page | number | 1 | initial page index | ✔ | ✔ | <3.0 | -| scale | number | 1.0 | should minScale<=scale<=maxScale| ✔ | ✔ | <3.0 | -| minScale | number | 1.0 | min scale| ✔ | ✔ | 5.0.5 | -| maxScale | number | 3.0 | max scale| ✔ | ✔ | 5.0.5 | -| horizontal | bool | false | draw page direction, if you want to listen the orientation change, you can use [[react-native-orientation-locker]](https://github.com/wonday/react-native-orientation-locker)| ✔ | ✔ | <3.0 | -| fitWidth | bool | false | if true fit the width of view, can not use fitWidth=true together with scale| ✔ | ✔ | <3.0, abandoned from 3.0 | -| fitPolicy | number | 2 | 0:fit width, 1:fit height, 2:fit both(default)| ✔ | ✔ | 3.0 | -| spacing | number | 10 | the breaker size between pages| ✔ | ✔ | <3.0 | -| password | string | "" | pdf password, if password error, will call OnError() with message "Password required or incorrect password." | ✔ | ✔ | <3.0 | -| style | object | {backgroundColor:"#eee"} | support normal view style, you can use this to set border/spacing color... | ✔ | ✔ | <3.0 | -| activityIndicator | Component | | when loading show it as an indicator, you can use your component| ✔ | ✔ | <3.0 | -| activityIndicatorProps | object | {color:'#009900', progressTintColor:'#009900'} | activityIndicator props | ✔ | ✔ | 3.1 | -| enableAntialiasing | bool | true | improve rendering a little bit on low-res screens, but maybe course some problem on Android 4.4, so add a switch | ✖ | ✔ | <3.0 | -| enablePaging | bool | false | only show one page in screen | ✔ | ✔ | 5.0.1 | -| enableRTL | bool | false | scroll page as "page3, page2, page1" | ✔ | ✖ | 5.0.1 | -| enableAnnotationRendering | bool | true | enable rendering annotation, notice:iOS only support initial setting,not support realtime changing | ✔ | ✔ | 5.0.3 | -| trustAllCerts | bool | true | Allow connections to servers with self-signed certification | ✔ | ✔ | 6.0.? | -| singlePage | bool | false | Only show first page, useful for thumbnail views | ✔ | ✔ | 6.1.2 | -| onLoadProgress | function(percent) | null | callback when loading, return loading progress (0-1) | ✔ | ✔ | <3.0 | -| onLoadComplete | function(numberOfPages, path, {width, height}, tableContents) | null | callback when pdf load completed, return total page count, pdf local/cache path, {width,height} and table of contents | ✔ | ✔ | <3.0 | -| onPageChanged | function(page,numberOfPages) | null | callback when page changed ,return current page and total page count | ✔ | ✔ | <3.0 | -| onError | function(error) | null | callback when error happened | ✔ | ✔ | <3.0 | -| onPageSingleTap | function(page) | null | callback when page was single tapped | ✔ | ✔ | 3.0 | -| onScaleChanged | function(scale) | null | callback when scale page | ✔ | ✔ | 3.0 | -| onPressLink | function(uri) | null | callback when link tapped | ✔ | ✔ | 6.0.0 | +| Property | Type | Default | Description | iOS | Android | Windows | FirstRelease | +| ------------- |:-------------:|:----------------:| ------------------- | ------| ------- | ------- | ------------ | +| source | object | not null | PDF source like {uri:xxx, cache:false}. see the following for detail.| ✔ | ✔ | partial | <3.0 | +| page | number | 1 | initial page index | ✔ | ✔ | ✔ | <3.0 | +| scale | number | 1.0 | should minScale<=scale<=maxScale| ✔ | ✔ | ✔ | <3.0 | +| minScale | number | 1.0 | min scale| ✔ | ✔ | ✔ | 5.0.5 | +| maxScale | number | 3.0 | max scale| ✔ | ✔ | ✔ | 5.0.5 | +| horizontal | bool | false | draw page direction, if you want to listen the orientation change, you can use [[react-native-orientation-locker]](https://github.com/wonday/react-native-orientation-locker)| ✔ | ✔ | ✔ | <3.0 | +| fitWidth | bool | false | if true fit the width of view, can not use fitWidth=true together with scale| ✔ | ✔ | ✔ | <3.0, abandoned from 3.0 | +| fitPolicy | number | 2 | 0:fit width, 1:fit height, 2:fit both(default)| ✔ | ✔ | ✔ | 3.0 | +| spacing | number | 10 | the breaker size between pages| ✔ | ✔ | ✔ | <3.0 | +| password | string | "" | pdf password, if password error, will call OnError() with message "Password required or incorrect password." | ✔ | ✔ | ✔ | <3.0 | +| style | object | {backgroundColor:"#eee"} | support normal view style, you can use this to set border/spacing color... | ✔ | ✔ | ✔ | <3.0 | +| activityIndicator | Component | | when loading show it as an indicator, you can use your component| ✔ | ✔ | ✖ | <3.0 | +| activityIndicatorProps | object | {color:'#009900', progressTintColor:'#009900'} | activityIndicator props | ✔ | ✔ | ✖ | 3.1 | +| enableAntialiasing | bool | true | improve rendering a little bit on low-res screens, but maybe course some problem on Android 4.4, so add a switch | ✖ | ✔ | ✖ | <3.0 | +| enablePaging | bool | false | only show one page in screen | ✔ | ✔ | ✔ | 5.0.1 | +| enableRTL | bool | false | scroll page as "page3, page2, page1" | ✔ | ✖ | ✔ | 5.0.1 | +| enableAnnotationRendering | bool | true | enable rendering annotation, notice:iOS only support initial setting,not support realtime changing | ✔ | ✔ | ✖ | 5.0.3 | +| trustAllCerts | bool | true | Allow connections to servers with self-signed certification | ✔ | ✔ | ✖ | 6.0.? | +| singlePage | bool | false | Only show first page, useful for thumbnail views | ✔ | ✔ | ✔ | 6.1.2 | +| onLoadProgress | function(percent) | null | callback when loading, return loading progress (0-1) | ✔ | ✔ | ✖ | <3.0 | +| onLoadComplete | function(numberOfPages, path, {width, height}, tableContents) | null | callback when pdf load completed, return total page count, pdf local/cache path, {width,height} and table of contents | ✔ | ✔ | ✔ but without tableContents | <3.0 | +| onPageChanged | function(page,numberOfPages) | null | callback when page changed ,return current page and total page count | ✔ | ✔ | ✔ | <3.0 | +| onError | function(error) | null | callback when error happened | ✔ | ✔ | ✔ | <3.0 | +| onPageSingleTap | function(page) | null | callback when page was single tapped | ✔ | ✔ | ✔ | 3.0 | +| onScaleChanged | function(scale) | null | callback when scale page | ✔ | ✔ | ✔ | 3.0 | +| onPressLink | function(uri) | null | callback when link tapped | ✔ | ✔ | ✖ | 6.0.0 | #### parameters of source -| parameter | Description | default | iOS | Android | -| ------------ | ----------- | ------- | --- | ------- | -| uri | pdf source, see the forllowing for detail.| required | ✔ | ✔ | -| cache | use cache or not | false | ✔ | ✔ | -| expiration | cache file expired seconds (0 is not expired) | 0 | ✔ | ✔ | -| method | request method when uri is a url | "GET" | ✔ | ✔ | -| headers | request headers when uri is a url | {} | ✔ | ✔ | +| parameter | Description | default | iOS | Android | Windows | +| ------------ | ----------- | ------- | --- | ------- | ------- | +| uri | pdf source, see the forllowing for detail.| required | ✔ | ✔ | ✔ | +| cache | use cache or not | false | ✔ | ✔ | ✖ | +| expiration | cache file expired seconds (0 is not expired) | 0 | ✔ | ✔ | ✖ | +| method | request method when uri is a url | "GET" | ✔ | ✔ | ✖ | +| headers | request headers when uri is a url | {} | ✔ | ✔ | ✖ | #### types of source.uri -| Usage | Description | iOS | Android | -| ------------ | ----------- | --- | ------- | -| `{uri:"http://xxx/xxx.pdf"}` | load pdf from a url | ✔ | ✔ | -| `{require("./test.pdf")}` | load pdf relate to js file (do not need add by xcode) | ✔ | ✖ | -| `{uri:"bundle-assets://path/to/xxx.pdf"}` | load pdf from assets, the file should be at android/app/src/main/assets/path/to/xxx.pdf | ✖ | ✔ | -| `{uri:"bundle-assets://xxx.pdf"}` | load pdf from assets, you must add pdf to project by xcode. this does not support folder. | ✔ | ✖ | -| `{uri:"data:application/pdf;base64,JVBERi0xLjcKJc..."}` | load pdf from base64 string | ✔ | ✔ | -| `{uri:"file:///absolute/path/to/xxx.pdf"}` | load pdf from local file system | ✔ | ✔ | +| Usage | Description | iOS | Android | Windows | +| ------------ | ----------- | --- | ------- | ------- | +| `{uri:"http://xxx/xxx.pdf"}` | load pdf from a url | ✔ | ✔ | ✖ | +| `{require("./test.pdf")}` | load pdf relate to js file (do not need add by xcode) | ✔ | ✖ | ✖ | +| `{uri:"bundle-assets://path/to/xxx.pdf"}` | load pdf from assets, the file should be at android/app/src/main/assets/path/to/xxx.pdf | ✖ | ✔ | ✖ | +| `{uri:"bundle-assets://xxx.pdf"}` | load pdf from assets, you must add pdf to project by xcode. this does not support folder. | ✔ | ✖ | ✖ | +| `{uri:"data:application/pdf;base64,JVBERi0xLjcKJc..."}` | load pdf from base64 string | ✔ | ✔ | ✖ | +| `{uri:"file:///absolute/path/to/xxx.pdf"}` | load pdf from local file system | ✔ | ✔ | ✔ | +| `{uri:"ms-appx:///xxx.pdf"}}` | load pdf bundled with UWP app | ✖ | ✖ | ✔ | ### Methods diff --git a/example/.gitignore b/example/.gitignore index 828cc884..f3bcfdf4 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -57,3 +57,67 @@ buck-out/ # CocoaPods /ios/Pods/ + +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +# Bundle artifact +*.jsbundle + +# CocoaPods +/ios/Pods/ + +# VS +*.binlog +*.ProjectImports.zip diff --git a/example/PDFExample.js b/example/PDFExample.js index e9dc4048..d9ef4980 100644 --- a/example/PDFExample.js +++ b/example/PDFExample.js @@ -83,6 +83,7 @@ export default class PDFExample extends React.Component { render() { let source = {uri:'http://samples.leanpub.com/thereactnativebook-sample.pdf',cache:true}; + //let source = {uri: 'ms-appx:///test.pdf'} //let source = require('./test.pdf'); // ios only //let source = {uri:'bundle-assets://test.pdf'}; diff --git a/example/metro.config.js b/example/metro.config.js index 13a96421..1b710a4d 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -4,8 +4,20 @@ * * @format */ +const path = require('path'); +const blacklist = require('metro-config/src/defaults/blacklist'); module.exports = { + resolver: { + blacklistRE: blacklist([ + // This stops "react-native run-windows" from causing the metro server to crash if its already running + new RegExp( + `${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`, + ), + // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip + /.*\.ProjectImports\.zip/, + ]), + }, transformer: { getTransformOptions: async () => ({ transform: { diff --git a/example/package.json b/example/package.json index 0595815c..173c3b15 100644 --- a/example/package.json +++ b/example/package.json @@ -20,10 +20,11 @@ "url": "https://github.com/wonday/react-native-pdf/issues" }, "dependencies": { - "react": "16.8.6", - "react-native": "0.60.4", + "react": "16.13.1", + "react-native": "0.63.2", "react-native-orientation-locker": "^1.1.6", - "react-native-pdf": "github:wonday/react-native-pdf#master", + "react-native-pdf": "file:../", + "react-native-windows": "^0.63.11", "rn-fetch-blob": "^0.10.16" }, "devDependencies": { diff --git a/example/windows/.gitignore b/example/windows/.gitignore new file mode 100644 index 00000000..4ea0c7b5 --- /dev/null +++ b/example/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/example/windows/PDFExample.sln b/example/windows/PDFExample.sln new file mode 100644 index 00000000..4116b9d5 --- /dev/null +++ b/example/windows/PDFExample.sln @@ -0,0 +1,264 @@ + +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}") = "PDFExample", "PDFExample\PDFExample.vcxproj", "{95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}" + 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 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ReactNative.Managed", "..\node_modules\react-native-windows\Microsoft.ReactNative.Managed\Microsoft.ReactNative.Managed.csproj", "{F2824844-CE15-4242-9420-308923CD76C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ReactNative.Managed.CodeGen", "..\node_modules\react-native-windows\Microsoft.ReactNative.Managed.CodeGen\Microsoft.ReactNative.Managed.CodeGen.csproj", "{ADED4FBE-887D-4271-AF24-F0823BCE7961}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCTPdf", "..\node_modules\react-native-pdf\windows\RCTPdf\RCTPdf.vcxproj", "{03B8503F-F40D-4013-829C-71B304537D90}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "progress-view", "..\node_modules\@react-native-community\progress-view\windows\progress-view\progress-view.vcxproj", "{CA54A654-5E81-44DC-AF3F-CF55EF5B493A}" +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*{ca54a654-5e81-44dc-af3f-cf55ef5b493a}*SharedItemsImports = 4 + ..\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 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|ARM.ActiveCfg = Debug|ARM + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|ARM.Build.0 = Debug|ARM + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|ARM.Deploy.0 = Debug|ARM + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|ARM64.Build.0 = Debug|ARM64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|x64.ActiveCfg = Debug|x64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|x64.Build.0 = Debug|x64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|x64.Deploy.0 = Debug|x64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|x86.ActiveCfg = Debug|Win32 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|x86.Build.0 = Debug|Win32 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Debug|x86.Deploy.0 = Debug|Win32 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|ARM.ActiveCfg = Release|ARM + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|ARM.Build.0 = Release|ARM + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|ARM.Deploy.0 = Release|ARM + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|ARM64.ActiveCfg = Release|ARM64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|ARM64.Build.0 = Release|ARM64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|ARM64.Deploy.0 = Release|ARM64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|x64.ActiveCfg = Release|x64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|x64.Build.0 = Release|x64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|x64.Deploy.0 = Release|x64 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|x86.ActiveCfg = Release|Win32 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.Release|x86.Build.0 = Release|Win32 + {95F9323C-1039-4B6A-B69E-A9EFDDFEFB54}.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 + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|ARM.ActiveCfg = Debug|ARM + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|ARM.Build.0 = Debug|ARM + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|ARM64.Build.0 = Debug|ARM64 + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|x64.ActiveCfg = Debug|x64 + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|x64.Build.0 = Debug|x64 + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|x86.ActiveCfg = Debug|x86 + {F2824844-CE15-4242-9420-308923CD76C3}.Debug|x86.Build.0 = Debug|x86 + {F2824844-CE15-4242-9420-308923CD76C3}.Release|ARM.ActiveCfg = Release|ARM + {F2824844-CE15-4242-9420-308923CD76C3}.Release|ARM.Build.0 = Release|ARM + {F2824844-CE15-4242-9420-308923CD76C3}.Release|ARM64.ActiveCfg = Release|ARM64 + {F2824844-CE15-4242-9420-308923CD76C3}.Release|ARM64.Build.0 = Release|ARM64 + {F2824844-CE15-4242-9420-308923CD76C3}.Release|x64.ActiveCfg = Release|x64 + {F2824844-CE15-4242-9420-308923CD76C3}.Release|x64.Build.0 = Release|x64 + {F2824844-CE15-4242-9420-308923CD76C3}.Release|x86.ActiveCfg = Release|x86 + {F2824844-CE15-4242-9420-308923CD76C3}.Release|x86.Build.0 = Release|x86 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|ARM.ActiveCfg = Debug|ARM + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|ARM.Build.0 = Debug|ARM + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|ARM64.Build.0 = Debug|ARM64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|x64.ActiveCfg = Debug|x64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|x64.Build.0 = Debug|x64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|x86.ActiveCfg = Debug|x86 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Debug|x86.Build.0 = Debug|x86 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|ARM.ActiveCfg = Release|ARM + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|ARM.Build.0 = Release|ARM + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|ARM64.ActiveCfg = Release|ARM64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|ARM64.Build.0 = Release|ARM64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|x64.ActiveCfg = Release|x64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|x64.Build.0 = Release|x64 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|x86.ActiveCfg = Release|x86 + {ADED4FBE-887D-4271-AF24-F0823BCE7961}.Release|x86.Build.0 = Release|x86 + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|ARM.ActiveCfg = Debug|ARM + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|ARM.Build.0 = Debug|ARM + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|ARM64.Build.0 = Debug|ARM64 + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|x64.ActiveCfg = Debug|x64 + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|x64.Build.0 = Debug|x64 + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|x86.ActiveCfg = Debug|Win32 + {03B8503F-F40D-4013-829C-71B304537D90}.Debug|x86.Build.0 = Debug|Win32 + {03B8503F-F40D-4013-829C-71B304537D90}.Release|ARM.ActiveCfg = Release|ARM + {03B8503F-F40D-4013-829C-71B304537D90}.Release|ARM.Build.0 = Release|ARM + {03B8503F-F40D-4013-829C-71B304537D90}.Release|ARM64.ActiveCfg = Release|ARM64 + {03B8503F-F40D-4013-829C-71B304537D90}.Release|ARM64.Build.0 = Release|ARM64 + {03B8503F-F40D-4013-829C-71B304537D90}.Release|x64.ActiveCfg = Release|x64 + {03B8503F-F40D-4013-829C-71B304537D90}.Release|x64.Build.0 = Release|x64 + {03B8503F-F40D-4013-829C-71B304537D90}.Release|x86.ActiveCfg = Release|Win32 + {03B8503F-F40D-4013-829C-71B304537D90}.Release|x86.Build.0 = Release|Win32 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|ARM.ActiveCfg = Debug|ARM + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|ARM.Build.0 = Debug|ARM + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|ARM64.Build.0 = Debug|ARM64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|x64.ActiveCfg = Debug|x64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|x64.Build.0 = Debug|x64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|x86.ActiveCfg = Debug|Win32 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|x86.Build.0 = Debug|Win32 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Debug|x86.Deploy.0 = Debug|Win32 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|ARM.ActiveCfg = Release|ARM + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|ARM.Build.0 = Release|ARM + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|ARM64.ActiveCfg = Release|ARM64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|ARM64.Build.0 = Release|ARM64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|x64.ActiveCfg = Release|x64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|x64.Build.0 = Release|x64 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|x86.ActiveCfg = Release|Win32 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|x86.Build.0 = Release|Win32 + {CA54A654-5E81-44DC-AF3F-CF55EF5B493A}.Release|x86.Deploy.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} + {F2824844-CE15-4242-9420-308923CD76C3} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {ADED4FBE-887D-4271-AF24-F0823BCE7961} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D43FAD39-F619-437D-BB40-04A3982ACB6A} + EndGlobalSection +EndGlobal diff --git a/example/windows/PDFExample/.gitignore b/example/windows/PDFExample/.gitignore new file mode 100644 index 00000000..917243bd --- /dev/null +++ b/example/windows/PDFExample/.gitignore @@ -0,0 +1 @@ +/Bundle diff --git a/example/windows/PDFExample/App.cpp b/example/windows/PDFExample/App.cpp new file mode 100644 index 00000000..d21b7c72 --- /dev/null +++ b/example/windows/PDFExample/App.cpp @@ -0,0 +1,80 @@ +#include "pch.h" + +#include "App.h" + +#include "AutolinkedNativeModules.g.h" +#include "ReactPackageProvider.h" + + +using namespace winrt::PDFExample; +using namespace winrt::PDFExample::implementation; +using namespace winrt; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::ApplicationModel; + +/// +/// Initializes the singleton application object. This is the first line of +/// authored code executed, and as such is the logical equivalent of main() or +/// WinMain(). +/// +App::App() noexcept +{ +#if BUNDLE + JavaScriptBundleFile(L"index.windows"); + InstanceSettings().UseWebDebugger(false); + InstanceSettings().UseFastRefresh(false); +#else + JavaScriptMainModuleName(L"index"); + InstanceSettings().UseWebDebugger(true); + InstanceSettings().UseFastRefresh(true); +#endif + +#if _DEBUG + InstanceSettings().UseDeveloperSupport(true); +#else + InstanceSettings().UseDeveloperSupport(false); +#endif + + RegisterAutolinkedNativeModulePackages(PackageProviders()); // Includes any autolinked modules + + PackageProviders().Append(make()); // Includes all modules in this project + PackageProviders().Append(winrt::progress_view::ReactPackageProvider()); + InitializeComponent(); +} + +/// +/// Invoked when the application is launched normally by the end user. Other entry points +/// will be used such as when the application is launched to open a specific file. +/// +/// Details about the launch request and process. +void App::OnLaunched(activation::LaunchActivatedEventArgs const& e) +{ + super::OnLaunched(e); + + Frame rootFrame = Window::Current().Content().as(); + rootFrame.Navigate(xaml_typename(), box_value(e.Arguments())); +} + +/// +/// Invoked when application execution is being suspended. Application state is saved +/// without knowing whether the application will be terminated or resumed with the contents +/// of memory still intact. +/// +/// The source of the suspend request. +/// Details about the suspend request. +void App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e) +{ + // Save application state and stop any background activity +} + +/// +/// Invoked when Navigation to a certain page fails +/// +/// The Frame which failed navigation +/// Details about the navigation failure +void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e) +{ + throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name); +} diff --git a/example/windows/PDFExample/App.h b/example/windows/PDFExample/App.h new file mode 100644 index 00000000..e94396f5 --- /dev/null +++ b/example/windows/PDFExample/App.h @@ -0,0 +1,20 @@ +#pragma once + +#include "App.xaml.g.h" + +namespace activation = winrt::Windows::ApplicationModel::Activation; + +namespace winrt::PDFExample::implementation +{ + struct App : AppT + { + App() noexcept; + void OnLaunched(activation::LaunchActivatedEventArgs const&); + void OnSuspending(IInspectable const&, Windows::ApplicationModel::SuspendingEventArgs const&); + void OnNavigationFailed(IInspectable const&, Windows::UI::Xaml::Navigation::NavigationFailedEventArgs const&); + private: + using super = AppT; + }; +} // namespace winrt::PDFExample::implementation + + diff --git a/example/windows/PDFExample/App.idl b/example/windows/PDFExample/App.idl new file mode 100644 index 00000000..0c75a56d --- /dev/null +++ b/example/windows/PDFExample/App.idl @@ -0,0 +1,3 @@ +namespace PDFExample +{ +} diff --git a/example/windows/PDFExample/App.xaml b/example/windows/PDFExample/App.xaml new file mode 100644 index 00000000..5117f6f4 --- /dev/null +++ b/example/windows/PDFExample/App.xaml @@ -0,0 +1,10 @@ + + + + + diff --git a/example/windows/PDFExample/Assets/LockScreenLogo.scale-200.png b/example/windows/PDFExample/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 00000000..735f57ad Binary files /dev/null and b/example/windows/PDFExample/Assets/LockScreenLogo.scale-200.png differ diff --git a/example/windows/PDFExample/Assets/SplashScreen.scale-200.png b/example/windows/PDFExample/Assets/SplashScreen.scale-200.png new file mode 100644 index 00000000..023e7f1f Binary files /dev/null and b/example/windows/PDFExample/Assets/SplashScreen.scale-200.png differ diff --git a/example/windows/PDFExample/Assets/Square150x150Logo.scale-200.png b/example/windows/PDFExample/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 00000000..af49fec1 Binary files /dev/null and b/example/windows/PDFExample/Assets/Square150x150Logo.scale-200.png differ diff --git a/example/windows/PDFExample/Assets/Square44x44Logo.scale-200.png b/example/windows/PDFExample/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 00000000..ce342a2e Binary files /dev/null and b/example/windows/PDFExample/Assets/Square44x44Logo.scale-200.png differ diff --git a/example/windows/PDFExample/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/example/windows/PDFExample/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 00000000..f6c02ce9 Binary files /dev/null and b/example/windows/PDFExample/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/example/windows/PDFExample/Assets/StoreLogo.png b/example/windows/PDFExample/Assets/StoreLogo.png new file mode 100644 index 00000000..7385b56c Binary files /dev/null and b/example/windows/PDFExample/Assets/StoreLogo.png differ diff --git a/example/windows/PDFExample/Assets/Wide310x150Logo.scale-200.png b/example/windows/PDFExample/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 00000000..288995b3 Binary files /dev/null and b/example/windows/PDFExample/Assets/Wide310x150Logo.scale-200.png differ diff --git a/example/windows/PDFExample/AutolinkedNativeModules.g.cpp b/example/windows/PDFExample/AutolinkedNativeModules.g.cpp new file mode 100644 index 00000000..58eabd79 --- /dev/null +++ b/example/windows/PDFExample/AutolinkedNativeModules.g.cpp @@ -0,0 +1,18 @@ +// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// clang-format off +#include "pch.h" +#include "AutolinkedNativeModules.g.h" + +// Includes from react-native-pdf +#include + +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{ + // IReactPackageProviders from react-native-pdf + packageProviders.Append(winrt::RCTPdf::ReactPackageProvider()); +} + +} diff --git a/example/windows/PDFExample/AutolinkedNativeModules.g.h b/example/windows/PDFExample/AutolinkedNativeModules.g.h new file mode 100644 index 00000000..1bf5f894 --- /dev/null +++ b/example/windows/PDFExample/AutolinkedNativeModules.g.h @@ -0,0 +1,10 @@ +// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" + +#pragma once + +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders); + +} diff --git a/example/windows/PDFExample/AutolinkedNativeModules.g.targets b/example/windows/PDFExample/AutolinkedNativeModules.g.targets new file mode 100644 index 00000000..c3cec5b0 --- /dev/null +++ b/example/windows/PDFExample/AutolinkedNativeModules.g.targets @@ -0,0 +1,10 @@ + + + + + + + {03b8503f-f40d-4013-829c-71b304537d90} + + + diff --git a/example/windows/PDFExample/MainPage.cpp b/example/windows/PDFExample/MainPage.cpp new file mode 100644 index 00000000..9027fe33 --- /dev/null +++ b/example/windows/PDFExample/MainPage.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "MainPage.h" +#if __has_include("MainPage.g.cpp") +#include "MainPage.g.cpp" +#endif + +#include "App.h" + + + +using namespace winrt; +using namespace Windows::UI::Xaml; + +namespace winrt::PDFExample::implementation +{ + MainPage::MainPage() + { + InitializeComponent(); + auto app = Application::Current().as(); + ReactRootView().ReactNativeHost(app->Host()); + } +} + + diff --git a/example/windows/PDFExample/MainPage.h b/example/windows/PDFExample/MainPage.h new file mode 100644 index 00000000..7c1579c9 --- /dev/null +++ b/example/windows/PDFExample/MainPage.h @@ -0,0 +1,21 @@ +#pragma once +#include "MainPage.g.h" +#include + + +namespace winrt::PDFExample::implementation +{ + struct MainPage : MainPageT + { + MainPage(); + }; +} + +namespace winrt::PDFExample::factory_implementation +{ + struct MainPage : MainPageT + { + }; +} + + diff --git a/example/windows/PDFExample/MainPage.idl b/example/windows/PDFExample/MainPage.idl new file mode 100644 index 00000000..dd74bbf0 --- /dev/null +++ b/example/windows/PDFExample/MainPage.idl @@ -0,0 +1,8 @@ +namespace PDFExample +{ + [default_interface] + runtimeclass MainPage : Windows.UI.Xaml.Controls.Page + { + MainPage(); + } +} diff --git a/example/windows/PDFExample/MainPage.xaml b/example/windows/PDFExample/MainPage.xaml new file mode 100644 index 00000000..37f0b163 --- /dev/null +++ b/example/windows/PDFExample/MainPage.xaml @@ -0,0 +1,16 @@ + + + diff --git a/example/windows/PDFExample/PDFExample.vcxproj b/example/windows/PDFExample/PDFExample.vcxproj new file mode 100644 index 00000000..b0eb7ee5 --- /dev/null +++ b/example/windows/PDFExample/PDFExample.vcxproj @@ -0,0 +1,205 @@ + + + + + true + true + true + {95f9323c-1039-4b6a-b69e-a9efddfefb54} + PDFExample + PDFExample + en-US + 16.0 + true + Windows Store + 10.0 + 10.0.18362.0 + 10.0.16299.0 + PDFExample_TemporaryKey.pfx + E03C74524183D74C00377AF0154C246C9456C18F + password + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), '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 + + + + Application + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + 4453;28204 + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + MainPage.xaml + Code + + + + + + App.xaml + + + + + Designer + + + + + Designer + + + + + + + + + + + + + + MainPage.xaml + Code + + + + + Create + + + App.xaml + + + + + + App.xaml + + + MainPage.xaml + Code + + + + + true + + + + + false + + + + + Designer + + + + + {ca54a654-5e81-44dc-af3f-cf55ef5b493a} + + + + + + + + + This project references targets in your node_modules\react-native-windows folder. 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/example/windows/PDFExample/PDFExample.vcxproj.filters b/example/windows/PDFExample/PDFExample.vcxproj.filters new file mode 100644 index 00000000..0f299790 --- /dev/null +++ b/example/windows/PDFExample/PDFExample.vcxproj.filters @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + + + + {e48dc53e-40b1-40cb-970a-f89935452892} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/windows/PDFExample/PDFExample_TemporaryKey.pfx b/example/windows/PDFExample/PDFExample_TemporaryKey.pfx new file mode 100644 index 00000000..2fb7facd Binary files /dev/null and b/example/windows/PDFExample/PDFExample_TemporaryKey.pfx differ diff --git a/example/windows/PDFExample/Package.appxmanifest b/example/windows/PDFExample/Package.appxmanifest new file mode 100644 index 00000000..65067f9a --- /dev/null +++ b/example/windows/PDFExample/Package.appxmanifest @@ -0,0 +1,52 @@ + + + + + + + + + + PDFExample + ja + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/windows/PDFExample/PropertySheet.props b/example/windows/PDFExample/PropertySheet.props new file mode 100644 index 00000000..5942ba39 --- /dev/null +++ b/example/windows/PDFExample/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/example/windows/PDFExample/ReactPackageProvider.cpp b/example/windows/PDFExample/ReactPackageProvider.cpp new file mode 100644 index 00000000..9a3b3bb0 --- /dev/null +++ b/example/windows/PDFExample/ReactPackageProvider.cpp @@ -0,0 +1,18 @@ +#include "pch.h" +#include "ReactPackageProvider.h" +#include "NativeModules.h" + + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::PDFExample::implementation +{ + +void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept +{ + AddAttributedModules(packageBuilder); +} + +} // namespace winrt::PDFExample::implementation + + diff --git a/example/windows/PDFExample/ReactPackageProvider.h b/example/windows/PDFExample/ReactPackageProvider.h new file mode 100644 index 00000000..1be3c878 --- /dev/null +++ b/example/windows/PDFExample/ReactPackageProvider.h @@ -0,0 +1,15 @@ +#pragma once + +#include "winrt/Microsoft.ReactNative.h" + + +namespace winrt::PDFExample::implementation +{ + struct ReactPackageProvider : winrt::implements + { + public: // IReactPackageProvider + void CreatePackage(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept; + }; +} // namespace winrt::PDFExample::implementation + + diff --git a/example/windows/PDFExample/packages.config b/example/windows/PDFExample/packages.config new file mode 100644 index 00000000..7ad3ffb8 --- /dev/null +++ b/example/windows/PDFExample/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/windows/PDFExample/pch.cpp b/example/windows/PDFExample/pch.cpp new file mode 100644 index 00000000..bcb5590b --- /dev/null +++ b/example/windows/PDFExample/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/example/windows/PDFExample/pch.h b/example/windows/PDFExample/pch.h new file mode 100644 index 00000000..e3d762be --- /dev/null +++ b/example/windows/PDFExample/pch.h @@ -0,0 +1,29 @@ +#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 "winrt/RCTPdf.h" +#include "winrt/progress_view.h" diff --git a/index.js b/index.js index edd49e82..740c3e72 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,16 @@ import { import { ProgressBar } from '@react-native-community/progress-bar-android' import { ProgressView } from '@react-native-community/progress-view' -import RNFetchBlob from 'rn-fetch-blob'; +let RNFetchBlob = { + fs : { + dirs: { + CacheDir: '' + } + } +}; +if (Platform.OS !== 'windows') { + RNFetchBlob = require('rn-fetch-blob'); +} const SHA1 = require('crypto-js/sha1'); import PdfView from './PdfView'; @@ -166,12 +175,10 @@ export default class Pdf extends Component { const source = Image.resolveAssetSource(newSource) || {}; let uri = source.uri || ''; - // first set to initial state if (this._mounted) { this.setState({isDownloaded: false, path: '', progress: 0}); } - const cacheFile = RNFetchBlob.fs.dirs.CacheDir + '/' + SHA1(uri) + '.pdf'; if (source.cache) { @@ -392,7 +399,7 @@ export default class Pdf extends Component { }; render() { - if (Platform.OS === "android" || Platform.OS === "ios") { + if (Platform.OS === "android" || Platform.OS === "ios" || Platform.OS === "windows") { return ( {!this.state.isDownloaded? @@ -415,7 +422,7 @@ export default class Pdf extends Component { {...this.props.activityIndicatorProps} />} ):( - Platform.OS === "android"?( + Platform.OS === "android" || Platform.OS === "windows"?( (this._root = component)} {...this.props} @@ -463,6 +470,10 @@ if (Platform.OS === "android") { var PdfCustom = requireNativeComponent('RCTPdfView', Pdf, { nativeOnly: {path: true, onChange: true}, }) +} else if (Platform.OS === "windows") { + var PdfCustom = requireNativeComponent('RCTPdf', Pdf, { + nativeOnly: {path: true, onChange: true}, + }) } diff --git a/windows/RCTPdf/PropertySheet.props b/windows/RCTPdf/PropertySheet.props new file mode 100644 index 00000000..5942ba39 --- /dev/null +++ b/windows/RCTPdf/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/windows/RCTPdf/RCTPdf.def b/windows/RCTPdf/RCTPdf.def new file mode 100644 index 00000000..24e7c123 --- /dev/null +++ b/windows/RCTPdf/RCTPdf.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/windows/RCTPdf/RCTPdf.vcxproj b/windows/RCTPdf/RCTPdf.vcxproj new file mode 100644 index 00000000..16839929 --- /dev/null +++ b/windows/RCTPdf/RCTPdf.vcxproj @@ -0,0 +1,180 @@ + + + + + true + true + true + {03b8503f-f40d-4013-829c-71b304537d90} + RCTPdf + RCTPdf + en-US + 16.0 + true + Windows Store + 10.0 + 10.0.18362.0 + 10.0.16299.0 + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), '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 + + /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) + 28204 + _WINRT_DLL;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + true + RCTPdf.def + + + + + _DEBUG;%(PreprocessorDefinitions) + ProgramDatabase + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + RCTPdfControl.xaml + Code + + + + + ReactPackageProvider.idl + + + + + RCTPdfControl.xaml + Code + + + Create + + + + ReactPackageProvider.idl + + + + + + RCTPdfControl.xaml + Code + + + + + + + + + + + + + Designer + + + + + + + + + This project references targets in your node_modules\react-native-windows folder. 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/RCTPdf/RCTPdf.vcxproj.filters b/windows/RCTPdf/RCTPdf.vcxproj.filters new file mode 100644 index 00000000..bd7347df --- /dev/null +++ b/windows/RCTPdf/RCTPdf.vcxproj.filters @@ -0,0 +1,38 @@ + + + + + accd3aa8-1ba0-4223-9bbe-0c431709210b + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + {926ab91d-31b5-48c3-b9a4-e681349f27f0} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/RCTPdf/RCTPdfControl.cpp b/windows/RCTPdf/RCTPdfControl.cpp new file mode 100644 index 00000000..e3a00d09 --- /dev/null +++ b/windows/RCTPdf/RCTPdfControl.cpp @@ -0,0 +1,662 @@ +#include "pch.h" +#include "RCTPdfControl.h" +#if __has_include("RCTPdfControl.g.cpp") +#include "RCTPdfControl.g.cpp" +#endif + +using namespace winrt; +using namespace Windows::UI::Xaml; +using namespace Microsoft::ReactNative; +using namespace Windows::Data::Json; +using namespace Windows::Data::Pdf; +using namespace Windows::Foundation; +using namespace Windows::Storage; +using namespace Windows::Storage::Streams; +using namespace Windows::Storage::Pickers; +using namespace Windows::UI; +using namespace Windows::UI::Core; +using namespace Windows::UI::Popups; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Input; +using namespace Windows::UI::Xaml::Media; +using namespace Windows::UI::Xaml::Media::Imaging; + +namespace winrt::RCTPdf::implementation +{ + PDFPageInfo::PDFPageInfo(winrt::Windows::UI::Xaml::Controls::Image image, winrt::Windows::Data::Pdf::PdfPage page, double imageScale, double renderScale) : + image(image), page(page), imageScale(imageScale), renderScale(renderScale), scaledTopOffset(0), scaledLeftOffset(0) { + auto dims = page.Size(); + height = (unsigned)dims.Height; + width = (unsigned)dims.Width; + scaledHeight = (unsigned)(height * imageScale); + scaledWidth = (unsigned)(width * imageScale); + } + PDFPageInfo::PDFPageInfo(const PDFPageInfo& rhs) : + height(rhs.height), width(rhs.width), scaledHeight(rhs.scaledHeight), scaledWidth(rhs.scaledWidth), + scaledTopOffset(rhs.scaledTopOffset), scaledLeftOffset(rhs.scaledTopOffset), imageScale(rhs.imageScale), + renderScale((double)rhs.renderScale), image(rhs.image), page(rhs.page) + { } + PDFPageInfo::PDFPageInfo(PDFPageInfo&& rhs) : + height(rhs.height), width(rhs.width), scaledHeight(rhs.scaledHeight), scaledWidth(rhs.scaledWidth), + scaledTopOffset(rhs.scaledTopOffset), scaledLeftOffset(rhs.scaledTopOffset), imageScale(rhs.imageScale), + renderScale((double)rhs.renderScale), image(std::move(rhs.image)), page(std::move(rhs.page)) + { } + unsigned PDFPageInfo::pageVisiblePixels(bool horizontal, double viewportStart, double viewportEnd) const { + if (viewportEnd < viewportStart) + std::swap(viewportStart, viewportEnd); + auto pageStart = horizontal ? scaledLeftOffset : scaledTopOffset; + auto pageSize = PDFPageInfo::pageSize(horizontal); + auto pageEnd = pageStart + pageSize; + auto uViewportStart = (unsigned)viewportStart; + auto uViewportEnd = (unsigned)viewportEnd; + if (pageStart >= uViewportStart && pageStart <= uViewportEnd) { // we see the top edge + return (std::min)(pageEnd, uViewportEnd) - pageStart; + } + if (pageEnd >= uViewportStart && pageEnd <= uViewportEnd) { // we see the bottom edge + return pageEnd - (std::max)(pageStart, uViewportStart); + } + if (pageStart <= uViewportStart && pageEnd >= uViewportEnd) {// we see the entire page + return uViewportEnd - uViewportStart; + } + return 0; + } + unsigned PDFPageInfo::pageSize(bool horizontal) const { + return horizontal ? scaledWidth : scaledHeight; + } + bool PDFPageInfo::needsRender() const { + double currentRenderScale = renderScale; + return currentRenderScale < imageScale || currentRenderScale > imageScale * m_downscaleTreshold; + } + winrt::IAsyncAction PDFPageInfo::render() { + return render(imageScale); + } + winrt::IAsyncAction PDFPageInfo::render(double useScale) { + double currentRenderScale; + while (true) { + currentRenderScale = renderScale; + if (!(currentRenderScale < imageScale || currentRenderScale > imageScale * m_downscaleTreshold)) + co_return; + if (renderScale.compare_exchange_weak(currentRenderScale, useScale)) + break; + if (renderScale > useScale) + co_return; + } + PdfPageRenderOptions renderOptions; + auto dims = page.Size(); + renderOptions.DestinationHeight(static_cast(dims.Height * useScale)); + renderOptions.DestinationWidth(static_cast(dims.Width * useScale)); + InMemoryRandomAccessStream stream; + co_await page.RenderToStreamAsync(stream, renderOptions); + BitmapImage bitmap; + co_await bitmap.SetSourceAsync(stream); + if (renderScale == useScale) + image.Source(bitmap); + } + + RCTPdfControl::RCTPdfControl(IReactContext const& reactContext) : m_reactContext(reactContext) { + InitializeComponent(); + } + + winrt::Windows::Foundation::Collections::IMapView RCTPdfControl::NativeProps() noexcept { + auto nativeProps = winrt::single_threaded_map(); + nativeProps.Insert(L"path", ViewManagerPropertyType::String); + nativeProps.Insert(L"page", ViewManagerPropertyType::Number); + nativeProps.Insert(L"scale", ViewManagerPropertyType::Number); + nativeProps.Insert(L"minScale", ViewManagerPropertyType::Number); + nativeProps.Insert(L"maxScale", ViewManagerPropertyType::Number); + nativeProps.Insert(L"horizontal", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"fitWidth", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"fitPolicy", ViewManagerPropertyType::Number); + nativeProps.Insert(L"spacing", ViewManagerPropertyType::Number); + nativeProps.Insert(L"password", ViewManagerPropertyType::String); + nativeProps.Insert(L"background", ViewManagerPropertyType::Color); + nativeProps.Insert(L"enablePaging", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"enableRTL", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"singlePage", ViewManagerPropertyType::Boolean); + + return nativeProps.GetView(); + } + + void RCTPdfControl::UpdateProperties(winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept { + const JSValueObject& propertyMap = JSValue::ReadObjectFrom(propertyMapReader); + std::optional pdfURI; + std::optional pdfPassword; + std::optional setPage; + std::optional minScale, maxScale, scale; + std::optional horizontal; + std::optional fitWidth; + std::optional fitPolicy; + std::optional spacing; + std::optional reverse; + std::optional singlePage; + std::optional enablePaging; + for (auto const& pair : propertyMap) { + auto const& propertyName = pair.first; + auto const& propertyValue = pair.second; + if (propertyName == "path") { + pdfURI = propertyValue != nullptr ? propertyValue.AsString() : ""; + } + else if (propertyName == "password") { + pdfPassword = propertyValue != nullptr ? propertyValue.AsString() : ""; + } + else if (propertyName == "page" && propertyValue != nullptr) { + setPage = propertyValue.AsInt32() - 1; + } + else if (propertyName == "scale" && propertyValue != nullptr) { + scale = propertyValue.AsDouble(); + } + else if (propertyName == "minScale" && propertyValue != nullptr) { + minScale = propertyValue.AsDouble(); + } + else if (propertyName == "maxScale" && propertyValue != nullptr) { + maxScale = propertyValue.AsDouble(); + } + else if (propertyName == "horizontal" && propertyValue != nullptr) { + horizontal = propertyValue.AsBoolean(); + } + else if (propertyName == "enablePaging" && propertyValue != nullptr) { + enablePaging = propertyValue.AsBoolean(); + } + else if (propertyName == "fitWidth" && propertyValue != nullptr) { + fitWidth = propertyValue.AsBoolean(); + } + else if (propertyName == "fitPolic" && propertyValue != nullptr) { + fitPolicy = propertyValue.AsInt32(); + } + else if (propertyName == "spacing" && propertyValue != nullptr) { + maxScale = propertyValue.AsInt32(); + } + else if (propertyName == "enableRTL" && propertyValue != nullptr) { + reverse = propertyValue.AsBoolean(); + } + else if (propertyName == "singlePage" && propertyValue != nullptr) { + singlePage = propertyValue.AsBoolean(); + } + else if (propertyName == "backgroundColor" && propertyValue != nullptr) { + auto color = propertyValue.AsInt32(); + winrt::Windows::UI::Color brushColor; + brushColor.A = (color >> 24) & 0xff; + brushColor.R = (color >> 16) & 0xff; + brushColor.G = (color >> 8) & 0xff; + brushColor.B = color & 0xff; + PagesContainer().Background(SolidColorBrush(brushColor)); + } + } + // If we are loading a new PDF: + std::shared_lock lock(m_rwlock); + if (pdfURI && *pdfURI != m_pdfURI || + pdfPassword && *pdfPassword != m_pdfPassword || + (reverse && *reverse != m_reverse) || + (enablePaging && *enablePaging != m_enablePaging) || + (singlePage && (m_pages.empty() || *singlePage && m_pages.size() != 1 || !*singlePage && m_pages.size() == 1)) ) { + lock.unlock(); + std::unique_lock write_lock(m_rwlock); + m_pdfURI = pdfURI.value_or(""); + m_pdfPassword = pdfPassword.value_or(""); + m_currentPage = setPage.value_or(0); + m_scale = scale.value_or(m_defualtZoom); + m_minScale = minScale.value_or(m_defaultMinZoom); + m_maxScale = maxScale.value_or(m_defaultMaxZoom); + m_horizontal = horizontal.value_or(false); + m_enablePaging = enablePaging.value_or(false); + int useFitPolicy = 2; + if (fitWidth) + useFitPolicy = 0; + if (fitPolicy) + useFitPolicy = *fitPolicy; + m_margins = spacing.value_or(m_defaultMargins); + m_reverse = reverse.value_or(false); + LoadPDF(std::move(write_lock), useFitPolicy, singlePage.value_or(false)); + } else { + // If we are updating the pdf: + m_minScale = minScale.value_or(m_minScale); + m_maxScale = maxScale.value_or(m_maxScale); + bool needScroll = false; + if (horizontal && *horizontal != m_horizontal) { + SetOrientation(*horizontal); + needScroll = true; + } + if (setPage) { + m_currentPage = *setPage; + needScroll = true; + } + if ((scale && *scale != m_scale) || (spacing && *spacing != m_margins)) { + Rescale(scale.value_or(m_scale), spacing.value_or(m_margins), !needScroll); + } + if (needScroll) { + GoToPage(m_currentPage); + } + } + } + + winrt::Microsoft::ReactNative::ConstantProviderDelegate RCTPdfControl::ExportedCustomBubblingEventTypeConstants() noexcept { + return [](IJSValueWriter const& constantWriter) { + WriteCustomDirectEventTypeConstant(constantWriter, "Change"); + }; + } + winrt::Microsoft::ReactNative::ConstantProviderDelegate RCTPdfControl::ExportedCustomDirectEventTypeConstants() noexcept { + return nullptr; + } + + winrt::Windows::Foundation::Collections::IVectorView RCTPdfControl::Commands() noexcept { + auto commands = winrt::single_threaded_vector(); + commands.Append(L"setPage"); + return commands.GetView(); + } + + void RCTPdfControl::DispatchCommand(winrt::hstring const& commandId, winrt::Microsoft::ReactNative::IJSValueReader const& commandArgsReader) noexcept { + auto commandArgs = JSValue::ReadArrayFrom(commandArgsReader); + if (commandId == L"setPage" && commandArgs.size() > 0) { + std::shared_lock lock(m_rwlock); + auto page = commandArgs[0].AsInt32() - 1; + GoToPage(page); + } + } + + void RCTPdfControl::PagesContainer_PointerWheelChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e) { + winrt::Windows::System::VirtualKeyModifiers modifiers = e.KeyModifiers(); + if ((modifiers & winrt::Windows::System::VirtualKeyModifiers::Control) != winrt::Windows::System::VirtualKeyModifiers::Control) + return; + double delta = (e.GetCurrentPoint(*this).Properties().MouseWheelDelta() / WHEEL_DELTA); + std::shared_lock lock(m_rwlock); + auto newScale = (std::max)((std::min)(m_scale * pow(m_zoomMultiplier, delta), m_maxScale), m_minScale); + Rescale(newScale, m_margins, true); + e.Handled(true); + } + + void RCTPdfControl::Pages_SizeChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::SizeChangedEventArgs const&) { + if (m_targetHorizontalOffset || m_targetVerticalOffset) { + auto container = PagesContainer(); + PagesContainer().ChangeView(m_targetHorizontalOffset.value_or(container.HorizontalOffset()), + m_targetVerticalOffset.value_or(container.VerticalOffset()), + nullptr, + true); + m_targetHorizontalOffset.reset(); + m_targetVerticalOffset.reset(); + } + } + + winrt::fire_and_forget RCTPdfControl::PagesContainer_ViewChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Controls::ScrollViewerViewChangedEventArgs const& args) + { + auto lifetime = get_strong(); + auto container = PagesContainer(); + auto currentHorizontalOffset = container.HorizontalOffset(); + auto currentVerticalOffset = container.VerticalOffset(); + double offsetStart = m_horizontal ? currentHorizontalOffset : currentVerticalOffset; + double viewSize = m_horizontal ? container.ViewportWidth() : container.ViewportHeight(); + double offsetEnd = offsetStart + viewSize; + std::shared_lock lock(m_rwlock, std::defer_lock); + if (args.IsIntermediate() || !lock.try_lock() || viewSize == 0 || m_pages.empty()) + return; + // Go through pages until we reach a visible page + int page = 0; + double visiblePagePixels = 0; + for (; page < (int)m_pages.size(); ++page) { + visiblePagePixels = m_pages[page].pageVisiblePixels(m_horizontal, offsetStart, offsetEnd); + if (visiblePagePixels > 0) + break; + } + if (page == (int)m_pages.size()) { + --page; + } + else { + double pagePixels = m_pages[page].pageSize(m_horizontal); + // #"page" is the first visible page. Check how much of the view port this page covers... + double viewCoveredByPage = visiblePagePixels / viewSize; + // ...and how much of the page is visible: + double pageVisiblePart = visiblePagePixels / pagePixels; + // If: + // - less than 50% of the screen is covered by the page + // - less than 50% of the page is visible (important if more than one page fits the screen) + // - there is a next page + // move the indicator to that page: + if (viewCoveredByPage < 0.5 && pageVisiblePart < 0.5 && page + 1 < (int)m_pages.size()) { + ++page; + } + } + // Render all visible pages - first the current one, then the next visible ones and one + // more, then the one before that might be partly visible, then one more before + co_await RenderVisiblePages(page); + if (page != m_currentPage) { + m_currentPage = page; + SignalPageChange(m_currentPage + 1, m_pages.size()); + } + } + + void RCTPdfControl::PagesContainer_Tapped(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Input::TappedRoutedEventArgs const& args) { + auto position = args.GetPosition(*this); + std::shared_lock lock(m_rwlock); + int scaledDoubleMargin = (int)(m_scale * m_margins * 2); + int page = 0; + int xPosition = (int)(position.X + PagesContainer().HorizontalOffset()); + int yPosition = (int)(position.Y + PagesContainer().VerticalOffset()); + for (; page < (int)m_pages.size(); ++page) { + if (m_horizontal) { + if (xPosition >= (int)m_pages[page].scaledLeftOffset && + xPosition <= (int)(m_pages[page].scaledLeftOffset + m_pages[page].scaledWidth + scaledDoubleMargin)) + break; + } else { + if (yPosition >= (int)m_pages[page].scaledTopOffset && + yPosition <= (int)(m_pages[page].scaledTopOffset + m_pages[page].scaledHeight + scaledDoubleMargin)) + break; + } + } + if (page == (int)m_pages.size()) { + page = m_currentPage; + } + SignalPageTapped(page, (int)position.X, (int)position.Y); + PagesContainer().Focus(FocusState::Pointer); + args.Handled(true); + } + + + void RCTPdfControl::PagesContainer_DoubleTapped(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& args) + { + std::shared_lock lock(m_rwlock); + double newScale = (std::min)(m_scale * m_zoomMultiplier, m_maxScale); + Rescale(newScale, m_margins, true); + PagesContainer().Focus(FocusState::Pointer); + args.Handled(true); + } + + void RCTPdfControl::ChangeScroll(double targetHorizontalOffset, double targetVerticalOffset) { + auto container = PagesContainer(); + auto maxHorizontalOffset = container.ScrollableWidth(); + auto maxVerticalOffset = container.ScrollableHeight(); + // If viewport is bigger than a page, it is possible that target offset is + // smaller than max offset. Take that into account: + if (targetHorizontalOffset <= maxHorizontalOffset + container.ViewportWidth() && + targetVerticalOffset <= maxVerticalOffset + container.ViewportHeight()) { + PagesContainer().ChangeView((std::min)(targetHorizontalOffset, maxHorizontalOffset), + (std::min)(targetVerticalOffset, maxVerticalOffset), + nullptr, true); + } + else { + m_targetHorizontalOffset = targetHorizontalOffset; + m_targetVerticalOffset = targetVerticalOffset; + } + } + + void RCTPdfControl::UpdatePagesInfoMarginOrScale() { + unsigned scaledMargin = (unsigned)(m_scale * m_margins); + for (auto& page : m_pages) { + page.imageScale = m_scale; + page.scaledWidth = (unsigned)(page.width * m_scale); + page.scaledHeight = (unsigned)(page.height * m_scale); + page.image.Margin(ThicknessHelper::FromUniformLength(scaledMargin)); + page.image.Width(page.scaledWidth); + page.image.Height(page.scaledHeight); + } + unsigned totalTopOffset = 0; + unsigned totalLeftOffset = 0; + unsigned doubleScaledMargin = scaledMargin * 2; + if (m_reverse) { + for (int page = m_pages.size() - 1; page >= 0; --page) { + m_pages[page].scaledTopOffset = totalTopOffset; + totalTopOffset += m_pages[page].scaledHeight + doubleScaledMargin; + m_pages[page].scaledLeftOffset = totalLeftOffset; + totalLeftOffset += m_pages[page].scaledWidth + doubleScaledMargin; + } + } + else { + for (int page = 0; page < (int)m_pages.size(); ++page) { + m_pages[page].scaledTopOffset = totalTopOffset; + totalTopOffset += m_pages[page].scaledHeight + doubleScaledMargin; + m_pages[page].scaledLeftOffset = totalLeftOffset; + totalLeftOffset += m_pages[page].scaledWidth + doubleScaledMargin; + } + } + } + + void RCTPdfControl::GoToPage(int page) { + if (page < 0 || page >= (int)m_pages.size()) { + return; + } + if (m_enablePaging) { + [&](int page) -> winrt::fire_and_forget { + auto lifetime = get_strong(); + co_await m_pages[page].render(); + }(page); + Pages().Items().ReplaceAll({ &m_pages[page].image, &m_pages[page].image + 1 }); + } else { + auto neededOffset = m_horizontal ? m_pages[page].scaledLeftOffset : m_pages[page].scaledTopOffset; + double horizontalOffset = m_horizontal ? neededOffset : PagesContainer().HorizontalOffset(); + double verticalOffset = m_horizontal ? PagesContainer().VerticalOffset() : neededOffset; + ChangeScroll(horizontalOffset, verticalOffset); + } + SignalPageChange(page + 1, m_pages.size()); + } + + winrt::fire_and_forget RCTPdfControl::LoadPDF(std::unique_lock lock, int fitPolicy, bool singlePage) { + auto lifetime = get_strong(); + auto pdfURI = m_pdfURI; + auto uri = Uri(winrt::to_hstring(pdfURI)); + auto scheme = uri.SchemeName(); + if (scheme == L"file") { + // backslashes only on Windows + std::replace(begin(pdfURI), end(pdfURI), '/', '\\'); + } + PdfDocument document = nullptr; + try { + auto file = scheme == L"file" + ? co_await StorageFile::GetFileFromPathAsync(winrt::to_hstring(pdfURI)) + : co_await StorageFile::GetFileFromApplicationUriAsync(uri); + document = co_await PdfDocument::LoadFromFileAsync(file, winrt::to_hstring(m_pdfPassword)); + } + catch (winrt::hresult_error const& ex) { + switch (ex.to_abi()) { + case __HRESULT_FROM_WIN32(ERROR_WRONG_PASSWORD): + SignalError("Password required or incorrect password."); + co_return; + case E_FAIL: + SignalError("Document is not a valid PDF."); + co_return; + default: + SignalError(winrt::to_string(ex.message())); + co_return; + } + } + if (!document) { + SignalError("Could not load PDF."); + co_return; + } + auto items = Pages().Items(); + items.Clear(); + m_pages.clear(); + SetOrientation(m_horizontal); + if (document.PageCount() == 0) { + if (fitPolicy != -1) + m_scale = 1; + } + else { + auto firstPageSize = document.GetPage(0).Size(); + auto viewWidth = PagesContainer().ViewportWidth(); + auto viewHeight = PagesContainer().ViewportHeight(); + switch (fitPolicy) { + case 0: + m_scale = viewWidth / (firstPageSize.Width + 2 * (double)m_margins); + break; + case 1: + m_scale = viewHeight / (firstPageSize.Height + 2 * (double)m_margins); + break; + case 2: + m_scale = (std::min)(viewWidth / (firstPageSize.Width + 2 * (double)m_margins), viewHeight / (firstPageSize.Height + 2 * (double)m_margins)); + break; + default: + break; + } + } + unsigned pagesCount = document.PageCount(); + if (singlePage && pagesCount > 0) + pagesCount = 1; + for (unsigned pageIdx = 0; pageIdx < pagesCount; ++pageIdx) { + auto page = document.GetPage(pageIdx); + auto dims = page.Size(); + Image pageImage; + pageImage.HorizontalAlignment(HorizontalAlignment::Center); + pageImage.AllowFocusOnInteraction(false); + m_pages.emplace_back(pageImage, page, m_scale, 0); + } + if (m_enablePaging) { + items.Append(m_pages[m_currentPage].image); + } else { + if (m_reverse) { + for (int page = m_pages.size() - 1; page >= 0; --page) + items.Append(m_pages[page].image); + } + else { + for (int page = 0; page < (int)m_pages.size(); ++page) + items.Append(m_pages[page].image); + } + } + UpdatePagesInfoMarginOrScale(); + lock.unlock(); + std::shared_lock shared_lock(m_rwlock); + if (m_currentPage < 0) + m_currentPage = 0; + if (m_currentPage < (int)m_pages.size()) { + co_await m_pages[m_currentPage].render(); + GoToPage(m_currentPage); + } + if (m_pages.empty()) { + SignalLoadComplete(0, 0, 0); + } + else { + SignalLoadComplete(m_pages.size(), m_pages.front().width, m_pages.front().height); + } + // Render low-res preview of the pages + double useScale = (std::min)(m_scale, m_previewZoom); + for (unsigned page = 0; page < m_pages.size(); ++page) { + co_await m_pages[page].render(useScale); + } + } + + void RCTPdfControl::Rescale(double newScale, double newMargin, bool goToNewPosition) { + if (newScale != m_scale || newMargin != m_margins) { + double rescale = newScale / m_scale; + double targetHorizontalOffset = PagesContainer().HorizontalOffset() * rescale; + double targetVerticalOffset = PagesContainer().VerticalOffset() * rescale; + if (newMargin != m_margins) { + if (m_horizontal) { + targetVerticalOffset += (double)m_currentPage * 2 * (newMargin - m_margins) * rescale; + } + else { + targetHorizontalOffset += (double)m_currentPage * 2 * (newMargin - m_margins) * rescale; + } + } + m_scale = newScale; + m_margins = (int)newMargin; + UpdatePagesInfoMarginOrScale(); + if (goToNewPosition) { + ChangeScroll(targetHorizontalOffset, targetVerticalOffset); + } + SignalScaleChanged(m_scale); + } + } + + void RCTPdfControl::SetOrientation(bool horizontal) { + m_horizontal = horizontal; + FindName(winrt::to_hstring("OrientationSelector")).try_as().Orientation(m_horizontal ? Orientation::Horizontal : Orientation::Vertical); + } + + winrt::IAsyncAction RCTPdfControl::RenderVisiblePages(int page) { + auto lifetime = get_strong(); + auto container = PagesContainer(); + auto currentHorizontalOffset = container.HorizontalOffset(); + auto currentVerticalOffset = container.VerticalOffset(); + double offsetStart = m_horizontal ? currentHorizontalOffset : currentVerticalOffset; + double viewSize = m_horizontal ? container.ViewportWidth() : container.ViewportHeight(); + double offsetEnd = offsetStart + viewSize; + if (m_pages[page].needsRender()) { + co_await m_pages[page].render(); + } + auto pageToRender = page + 1; + while (pageToRender < (int)m_pages.size() && + m_pages[pageToRender].pageVisiblePixels(m_horizontal, offsetStart, offsetEnd) > 0) { + if (m_pages[pageToRender].needsRender()) { + co_await m_pages[pageToRender].render(); + } + ++pageToRender; + } + if (pageToRender < (int)m_pages.size() && m_pages[pageToRender].needsRender()) { + co_await m_pages[pageToRender].render(); + } + if (page >= 1 && m_pages[page - 1].needsRender()) { + co_await m_pages[page - 1].render(); + } + if (page >= 2 && m_pages[page - 2].needsRender()) { + co_await m_pages[page - 2].render(); + } + } + + void RCTPdfControl::SignalError(const std::string& error) { + m_reactContext.DispatchEvent( + *this, + L"topChange", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { + eventDataWriter.WriteObjectBegin(); + { + WriteProperty(eventDataWriter, L"message", winrt::to_hstring("error|" + error)); + } + eventDataWriter.WriteObjectEnd(); + }); + } + void RCTPdfControl::SignalLoadComplete(int totalPages, int width, int height) { + auto message = "loadComplete|" + + std::to_string(totalPages) + "|" + + std::to_string(width) + "|" + + std::to_string(height) + "|"; + m_reactContext.DispatchEvent( + *this, + L"topChange", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { + eventDataWriter.WriteObjectBegin(); + { + WriteProperty(eventDataWriter, L"message", winrt::to_hstring(message)); + } + eventDataWriter.WriteObjectEnd(); + }); + } + void RCTPdfControl::SignalPageChange(int page, int totalPages) { + auto message = "pageChanged|" + std::to_string(page) + "|" + std::to_string(totalPages); + m_reactContext.DispatchEvent( + *this, + L"topChange", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { + eventDataWriter.WriteObjectBegin(); + { + WriteProperty(eventDataWriter, L"message", winrt::to_hstring(message)); + } + eventDataWriter.WriteObjectEnd(); + }); + } + void RCTPdfControl::SignalScaleChanged(double scale) { + m_reactContext.DispatchEvent( + *this, + L"topChange", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { + eventDataWriter.WriteObjectBegin(); + { + WriteProperty(eventDataWriter, L"message", winrt::to_hstring("scale|" + std::to_string(scale))); + } + eventDataWriter.WriteObjectEnd(); + }); + } + void RCTPdfControl::SignalPageTapped(int page, int x, int y) { + const std::string message = "pageSingleTap|" + + std::to_string(page + 1) + "|" + + std::to_string(x) + "|" + + std::to_string(y); + m_reactContext.DispatchEvent( + *this, + L"topChange", + [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { + eventDataWriter.WriteObjectBegin(); + { + WriteProperty(eventDataWriter, L"message", winrt::to_hstring(message)); + } + eventDataWriter.WriteObjectEnd(); + }); + } +} diff --git a/windows/RCTPdf/RCTPdfControl.h b/windows/RCTPdf/RCTPdfControl.h new file mode 100644 index 00000000..9a135ec1 --- /dev/null +++ b/windows/RCTPdf/RCTPdfControl.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include "winrt/Windows.UI.Xaml.h" +#include "winrt/Windows.UI.Xaml.Markup.h" +#include "winrt/Windows.UI.Xaml.Interop.h" +#include "winrt/Windows.UI.Xaml.Controls.Primitives.h" +#include "winrt/Microsoft.ReactNative.h" +#include "NativeModules.h" +#include "RCTPdfControl.g.h" + +namespace winrt::RCTPdf::implementation +{ + struct PDFPageInfo { + PDFPageInfo(winrt::Windows::UI::Xaml::Controls::Image image, winrt::Windows::Data::Pdf::PdfPage page, double imageScale, double renderScale); + PDFPageInfo(const PDFPageInfo&); + PDFPageInfo(PDFPageInfo&&); + unsigned pageVisiblePixels(bool horizontal, double viewportStart, double viewportEnd) const; + unsigned pageSize(bool horizontal) const; + bool needsRender() const; + winrt::IAsyncAction render(); + winrt::IAsyncAction render(double useScale); + unsigned height, width; + unsigned scaledHeight, scaledWidth; + unsigned scaledTopOffset, scaledLeftOffset; + double imageScale; // scale at which the image is displayed + // Multiple tasks can update the image, use the render scale as the sync point + std::atomic renderScale; // scale at which the image is rendered + winrt::Windows::UI::Xaml::Controls::Image image; + winrt::Windows::Data::Pdf::PdfPage page; + + // If zooming-out at what point we rerender the image with smaller scale? + // E.g. value of 2 means if the image is currently rendered at scale 1.0 + // we will rerender it when the scale is smaller than 0.5 + static constexpr double m_downscaleTreshold = 2; + }; + + struct RCTPdfControl : RCTPdfControlT + { + public: + RCTPdfControl(Microsoft::ReactNative::IReactContext const& reactContext); + + static winrt::Windows::Foundation::Collections::IMapView NativeProps() noexcept; + void UpdateProperties(winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept; + + static winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept; + static winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept; + + static winrt::Windows::Foundation::Collections::IVectorView Commands() noexcept; + void DispatchCommand(winrt::hstring const& commandId, winrt::Microsoft::ReactNative::IJSValueReader const& commandArgsReader) noexcept; + + void PagesContainer_PointerWheelChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); + void Pages_SizeChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs const& e); + winrt::fire_and_forget PagesContainer_ViewChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::ScrollViewerViewChangedEventArgs const& e); + void PagesContainer_Tapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e); + void PagesContainer_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e); + private: + Microsoft::ReactNative::IReactContext m_reactContext{ nullptr }; + + // Global lock to access the data stuff + // the pages are rendered in an async way + std::shared_mutex m_rwlock; + // URI and password of the PDF + std::string m_pdfURI; + std::string m_pdfPassword; + // Current active page + int m_currentPage = 0; + // Margins of each page + int m_margins = 10; + // Scale at which the PDF is displayed + double m_scale = 0.2; + double m_minScale = 0.1; + double m_maxScale = 3.0; + // Are we in "horizontal" mode? + bool m_horizontal = false; + // Render the pages in reverse order + bool m_reverse = false; + // Is in "enablePaging" mode + bool m_enablePaging = false; + + // When we rescale or change the margins, we can jump to the new position in the view + // only after the ScrollViewer has updated. We store the target offsets here, and go + // to them when the control finishes updating; + std::optional m_targetHorizontalOffset; + std::optional m_targetVerticalOffset; + // It is possible, that the new position is reachable even before the control is updated. + // A helper function that either schedules a change in the view or jumps right to + // the position + void ChangeScroll(double targetHorizontalOffset, double targetVerticalOffset); + + // Pages info + std::vector m_pages; + + void UpdatePagesInfoMarginOrScale(); + winrt::fire_and_forget LoadPDF(std::unique_lock lock, int fitPolicy, bool singlePage); + void GoToPage(int page); + void Rescale(double newScale, double newMargin, bool goToNewPosition); + void SetOrientation(bool horizontal); + winrt::IAsyncAction RenderVisiblePages(int page); + void SignalError(const std::string& error); + void SignalLoadComplete(int totalPages, int width, int height); + void SignalPageChange(int page, int totalPages); + void SignalScaleChanged(double scale); + void SignalPageTapped(int page, int x, int y); + + // Zoom in/out scale multiplier + static constexpr double m_zoomMultiplier = 1.2; + static constexpr double m_defaultMaxZoom = 3.0; + static constexpr double m_defaultMinZoom = 1.0; + static constexpr double m_defualtZoom = 1.0; + static constexpr int m_defaultMargins = 10; + static constexpr double m_previewZoom = 0.5; + }; +} + +namespace winrt::RCTPdf::factory_implementation { + struct RCTPdfControl : RCTPdfControlT { + }; +} diff --git a/windows/RCTPdf/RCTPdfControl.idl b/windows/RCTPdf/RCTPdfControl.idl new file mode 100644 index 00000000..0d0591d3 --- /dev/null +++ b/windows/RCTPdf/RCTPdfControl.idl @@ -0,0 +1,10 @@ +namespace RCTPdf +{ + [default_interface] + runtimeclass RCTPdfControl : Windows.UI.Xaml.Controls.UserControl + { + RCTPdfControl(Microsoft.ReactNative.IReactContext context); + void UpdateProperties(Microsoft.ReactNative.IJSValueReader reader); + void DispatchCommand(String commandId, Microsoft.ReactNative.IJSValueReader commandArgsReader); + } +} diff --git a/windows/RCTPdf/RCTPdfControl.xaml b/windows/RCTPdf/RCTPdfControl.xaml new file mode 100644 index 00000000..b5466138 --- /dev/null +++ b/windows/RCTPdf/RCTPdfControl.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/windows/RCTPdf/RCTPdfViewManager.cpp b/windows/RCTPdf/RCTPdfViewManager.cpp new file mode 100644 index 00000000..b1d1386a --- /dev/null +++ b/windows/RCTPdf/RCTPdfViewManager.cpp @@ -0,0 +1,69 @@ +#include "pch.h" +#include "NativeModules.h" +#include "JSValueXaml.h" +#include "RCTPdfViewManager.h" +#include "RCTPdfControl.h" + +namespace winrt { + using namespace Microsoft::ReactNative; + using namespace Windows::Foundation; + using namespace Windows::Foundation::Collections; + using namespace Windows::UI; + using namespace Windows::UI::Xaml; + using namespace Windows::UI::Xaml::Controls; +} + +namespace winrt::RCTPdf::implementation { + // IViewManager + winrt::hstring RCTPdfViewManager::Name() noexcept { + return L"RCTPdf"; + } + + winrt::FrameworkElement RCTPdfViewManager::CreateView() noexcept { + return winrt::RCTPdf::RCTPdfControl(m_reactContext); + } + + // IViewManagerWithReactContext + winrt::IReactContext RCTPdfViewManager::ReactContext() noexcept { + return m_reactContext; + } + + void RCTPdfViewManager::ReactContext(IReactContext reactContext) noexcept { + m_reactContext = reactContext; + } + + // IViewManagerWithNativeProperties + IMapView RCTPdfViewManager::NativeProps() noexcept { + return winrt::RCTPdf::implementation::RCTPdfControl::NativeProps(); + } + + void RCTPdfViewManager::UpdateProperties( + FrameworkElement const& view, + IJSValueReader const& propertyMapReader) noexcept { + if (auto module = view.try_as()) { + module.UpdateProperties(propertyMapReader); + } + } + // IViewManagerWithExportedEventTypeConstants + ConstantProviderDelegate RCTPdfViewManager::ExportedCustomBubblingEventTypeConstants() noexcept { + return winrt::RCTPdf::implementation::RCTPdfControl::ExportedCustomBubblingEventTypeConstants(); + } + + ConstantProviderDelegate RCTPdfViewManager::ExportedCustomDirectEventTypeConstants() noexcept { + return winrt::RCTPdf::implementation::RCTPdfControl::ExportedCustomDirectEventTypeConstants(); + } + + // IViewManagerWithCommands + IVectorView RCTPdfViewManager::Commands() noexcept { + return winrt::RCTPdf::implementation::RCTPdfControl::Commands(); + } + + void RCTPdfViewManager::DispatchCommand( + FrameworkElement const& view, + winrt::hstring const& commandId, + winrt::IJSValueReader const& commandArgsReader) noexcept { + if (auto module = view.try_as()) { + module.DispatchCommand(commandId, commandArgsReader); + } + } +} diff --git a/windows/RCTPdf/RCTPdfViewManager.h b/windows/RCTPdf/RCTPdfViewManager.h new file mode 100644 index 00000000..fa2aaee0 --- /dev/null +++ b/windows/RCTPdf/RCTPdfViewManager.h @@ -0,0 +1,51 @@ +#pragma once +#include "winrt/Microsoft.ReactNative.h" +#include "NativeModules.h" + + +namespace winrt::RCTPdf::implementation { + + class RCTPdfViewManager : public winrt::implements< + RCTPdfViewManager, + winrt::Microsoft::ReactNative::IViewManager, + winrt::Microsoft::ReactNative::IViewManagerWithReactContext, + winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties, + winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants, + winrt::Microsoft::ReactNative::IViewManagerWithCommands> { + public: + RCTPdfViewManager() = default; + + // IViewManager + winrt::hstring Name() noexcept; + winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept; + + // IViewManagerWithReactContext + winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept; + void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept; + + // IViewManagerWithNativeProperties + winrt::Windows::Foundation::Collections:: + IMapView + NativeProps() noexcept; + + void UpdateProperties( + winrt::Windows::UI::Xaml::FrameworkElement const& view, + winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept; + + // IViewManagerWithExportedEventTypeConstants + winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept; + winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept; + + + // IViewManagerWithCommands + winrt::Windows::Foundation::Collections::IVectorView Commands() noexcept; + + void DispatchCommand( + winrt::Windows::UI::Xaml::FrameworkElement const &view, + winrt::hstring const &commandId, + winrt::Microsoft::ReactNative::IJSValueReader const &commandArgsReader) noexcept; + + private: + winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr }; + }; +} diff --git a/windows/RCTPdf/ReactPackageProvider.cpp b/windows/RCTPdf/ReactPackageProvider.cpp new file mode 100644 index 00000000..af554dd0 --- /dev/null +++ b/windows/RCTPdf/ReactPackageProvider.cpp @@ -0,0 +1,15 @@ +#include "pch.h" +#include "ReactPackageProvider.h" +#if __has_include("ReactPackageProvider.g.cpp") +# include "ReactPackageProvider.g.cpp" +#endif + +#include "RCTPdfViewManager.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::RCTPdf::implementation { + void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept { + packageBuilder.AddViewManager(L"RCTPdfViewManager", []() { return winrt::make(); }); + } +} diff --git a/windows/RCTPdf/ReactPackageProvider.h b/windows/RCTPdf/ReactPackageProvider.h new file mode 100644 index 00000000..0ce5b9c5 --- /dev/null +++ b/windows/RCTPdf/ReactPackageProvider.h @@ -0,0 +1,16 @@ +#pragma once +#include "ReactPackageProvider.g.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::RCTPdf::implementation { + struct ReactPackageProvider : ReactPackageProviderT { + ReactPackageProvider() = default; + void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept; + }; +} + +namespace winrt::RCTPdf::factory_implementation { + struct ReactPackageProvider : ReactPackageProviderT {}; +} + diff --git a/windows/RCTPdf/ReactPackageProvider.idl b/windows/RCTPdf/ReactPackageProvider.idl new file mode 100644 index 00000000..8f2b9f54 --- /dev/null +++ b/windows/RCTPdf/ReactPackageProvider.idl @@ -0,0 +1,9 @@ +namespace RCTPdf +{ + [webhosthidden] + [default_interface] + runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider + { + ReactPackageProvider(); + }; +} diff --git a/windows/RCTPdf/packages.config b/windows/RCTPdf/packages.config new file mode 100644 index 00000000..1447e714 --- /dev/null +++ b/windows/RCTPdf/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/windows/RCTPdf/pch.cpp b/windows/RCTPdf/pch.cpp new file mode 100644 index 00000000..bcb5590b --- /dev/null +++ b/windows/RCTPdf/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/windows/RCTPdf/pch.h b/windows/RCTPdf/pch.h new file mode 100644 index 00000000..ebe005ec --- /dev/null +++ b/windows/RCTPdf/pch.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/windows/README.md b/windows/README.md new file mode 100644 index 00000000..8bc2611b --- /dev/null +++ b/windows/README.md @@ -0,0 +1,23 @@ +# react-native-pdf Windows Implementation + +Since the module uses react-native-progress-view, it also needs to be referenced in the App: +- Open your solution in Visual Studio 2019 (eg. `windows\yourapp.sln`) +- Right-click Solution icon in Solution Explorer > Add > Existing Project... +- Add `node_modules\@react-native-community\progress-view\windows\progress-view\progress-view.vcxproj` +- If running RNW 0.62: add `node_modules\react-native-pdf\windows\RCTPdf\RCTPdf.vcxproj` +- Right-click main application project > Add > Reference... + - Select `progress-view` and in Solution Projects + - If running 0.62, also select `RCTPdf` +- In app `pch.h` add `#include "winrt/progress-view.h"` and `#include "winrt/RCTPdf.h"` +- In `App.cpp` add `PackageProviders().Append(winrt::progress-view::ReactPackageProvider());` before `InitializeComponent();` +- If running RNW 0.62, also add `PackageProviders().Append(winrt::RCTPdf::ReactPackageProvider());` + + +## Bundling PDFs with the app +To add a `test.pdf` like in the example add: +``` + + true + +``` +in the app `.vcxproj` file, before ``.