@iftek/react-native-print 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/stale.yml +16 -0
- package/.github/workflows/windows-ci.yml +40 -0
- package/@iftek-react-native-print.podspec +19 -0
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/android/.gradle/7.4/checksums/checksums.lock +0 -0
- package/android/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/7.4/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/7.4/fileChanges/last-build.bin +0 -0
- package/android/.gradle/7.4/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/7.4/gc.properties +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +24 -0
- package/android/local.properties +8 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/christopherdro/RNPrint/RNPrintModule.java +237 -0
- package/android/src/main/java/com/christopherdro/RNPrint/RNPrintPackage.java +32 -0
- package/index.js +3 -0
- package/ios/RNPrint/RNPrint.h +15 -0
- package/ios/RNPrint/RNPrint.m +197 -0
- package/ios/RNPrint.xcodeproj/project.pbxproj +389 -0
- package/ios/RNPrint.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/RNPrint.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/RNPrintTests/Info.plist +24 -0
- package/package.json +23 -0
- package/scripts/examples_postinstall.js +117 -0
- package/types/index.d.ts +19 -0
- package/windows/README.md +57 -0
- package/windows/RNPrint/PropertySheet.props +16 -0
- package/windows/RNPrint/RNPrint.cpp +375 -0
- package/windows/RNPrint/RNPrint.def +3 -0
- package/windows/RNPrint/RNPrint.h +89 -0
- package/windows/RNPrint/RNPrint.vcxproj +163 -0
- package/windows/RNPrint/RNPrint.vcxproj.filters +33 -0
- package/windows/RNPrint/ReactPackageProvider.cpp +15 -0
- package/windows/RNPrint/ReactPackageProvider.h +16 -0
- package/windows/RNPrint/ReactPackageProvider.idl +9 -0
- package/windows/RNPrint/packages.config +4 -0
- package/windows/RNPrint/pch.cpp +1 -0
- package/windows/RNPrint/pch.h +16 -0
- package/windows/RNPrint62.sln +254 -0
- package/windows/RNPrint63.sln +226 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/*
|
4
|
+
* Using libraries within examples and linking them within packages.json like:
|
5
|
+
* "react-native-library-name": "file:../"
|
6
|
+
* will cause problems with the metro bundler if the example will run via
|
7
|
+
* `react-native run-[ios|android]`. This will result in an error as the metro
|
8
|
+
* bundler will find multiple versions for the same module while resolving it.
|
9
|
+
* The reason for that is that if the library is installed it also copies in the
|
10
|
+
* example folder itself as well as the node_modules folder of the library
|
11
|
+
* although their are defined in .npmignore and should be ignored in theory.
|
12
|
+
*
|
13
|
+
* This postinstall script removes the node_modules folder as well as all
|
14
|
+
* entries from the libraries .npmignore file within the examples node_modules
|
15
|
+
* folder after the library was installed. This should resolve the metro
|
16
|
+
* bundler issue mentioned above.
|
17
|
+
*
|
18
|
+
* It is expected this scripts lives in the libraries root folder within a
|
19
|
+
* scripts folder. As first parameter the relative path to the libraries
|
20
|
+
* folder within the example's node_modules folder may be provided.
|
21
|
+
* This script will determine the path from this project's package.json file
|
22
|
+
* if no such relative path is provided.
|
23
|
+
* An example's package.json entry could look like:
|
24
|
+
* "postinstall": "node ../scripts/examples_postinstall.js node_modules/react-native-library-name/"
|
25
|
+
*/
|
26
|
+
|
27
|
+
'use strict';
|
28
|
+
|
29
|
+
const fs = require('fs');
|
30
|
+
const path = require('path');
|
31
|
+
|
32
|
+
/// Delete all files and directories for the given path
|
33
|
+
const removeFileDirectoryRecursively = fileDirPath => {
|
34
|
+
// Remove file
|
35
|
+
if (!fs.lstatSync(fileDirPath).isDirectory()) {
|
36
|
+
fs.unlinkSync(fileDirPath);
|
37
|
+
return;
|
38
|
+
}
|
39
|
+
|
40
|
+
// Go down the directory an remove each file / directory recursively
|
41
|
+
fs.readdirSync(fileDirPath).forEach(entry => {
|
42
|
+
const entryPath = path.join(fileDirPath, entry);
|
43
|
+
removeFileDirectoryRecursively(entryPath);
|
44
|
+
});
|
45
|
+
fs.rmdirSync(fileDirPath);
|
46
|
+
};
|
47
|
+
|
48
|
+
const removeFileDir = fileDirPath => {
|
49
|
+
try {
|
50
|
+
removeFileDirectoryRecursively(fileDirPath);
|
51
|
+
console.log(`Deleted: ${fileDirPath}`);
|
52
|
+
} catch (err) {
|
53
|
+
console.log(`Error deleting ${fileDirPath}: ${err.message}`);
|
54
|
+
}
|
55
|
+
};
|
56
|
+
|
57
|
+
/// Remove example/node_modules/react-native-library-name/node_modules directory
|
58
|
+
const removeLibraryNodeModulesPath = nodeModulesPath => {
|
59
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
60
|
+
console.log(
|
61
|
+
`No node_modules found at ${nodeModulesPath}. Skipping delete.`,
|
62
|
+
);
|
63
|
+
return;
|
64
|
+
}
|
65
|
+
|
66
|
+
removeFileDir(nodeModulesPath);
|
67
|
+
};
|
68
|
+
|
69
|
+
/// Remove all entries from the .npmignore within example/node_modules/react-native-library-name/
|
70
|
+
const removeLibraryNpmIgnorePaths = (npmIgnorePath, libraryNodeModulesPath) => {
|
71
|
+
if (!fs.existsSync(npmIgnorePath)) {
|
72
|
+
console.log(
|
73
|
+
`No .npmignore found at ${npmIgnorePath}. Skipping deleting content.`,
|
74
|
+
);
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
|
78
|
+
fs.readFileSync(npmIgnorePath, 'utf8')
|
79
|
+
.split(/\r?\n/)
|
80
|
+
.forEach(entry => {
|
81
|
+
if (entry.length === 0) {
|
82
|
+
return;
|
83
|
+
}
|
84
|
+
|
85
|
+
const npmIgnoreLibraryNodeModulesEntryPath = path.resolve(
|
86
|
+
libraryNodeModulesPath,
|
87
|
+
entry,
|
88
|
+
);
|
89
|
+
if (!fs.existsSync(npmIgnoreLibraryNodeModulesEntryPath)) {
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
|
93
|
+
removeFileDir(npmIgnoreLibraryNodeModulesEntryPath);
|
94
|
+
});
|
95
|
+
};
|
96
|
+
|
97
|
+
// Main start sweeping process
|
98
|
+
(() => {
|
99
|
+
// Read out dir of example project
|
100
|
+
const cwd = process.cwd();
|
101
|
+
|
102
|
+
console.log(`Starting postinstall cleanup for ${cwd}`);
|
103
|
+
|
104
|
+
// Resolve the React Native library's path within the example's node_modules directory
|
105
|
+
const libraryNodeModulesPath =
|
106
|
+
process.argv.length > 2
|
107
|
+
? path.resolve(cwd, process.argv[2])
|
108
|
+
: path.resolve(cwd, 'node_modules', require('../package.json').name);
|
109
|
+
|
110
|
+
removeLibraryNodeModulesPath(
|
111
|
+
path.resolve(libraryNodeModulesPath, 'node_modules'),
|
112
|
+
);
|
113
|
+
removeLibraryNpmIgnorePaths(
|
114
|
+
path.resolve(__dirname, '../.npmignore'),
|
115
|
+
libraryNodeModulesPath,
|
116
|
+
);
|
117
|
+
})();
|
package/types/index.d.ts
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
type PrintOptionsType = {
|
2
|
+
printerURL?: string;
|
3
|
+
isLandscape?: boolean;
|
4
|
+
jobName?: string;
|
5
|
+
} & ({ html: string } | { filePath: string });
|
6
|
+
|
7
|
+
type SelectPrinterOptionsType = {
|
8
|
+
x: number;
|
9
|
+
y: number;
|
10
|
+
};
|
11
|
+
|
12
|
+
type SelectPrinterReturnType = {
|
13
|
+
name: string;
|
14
|
+
url: string;
|
15
|
+
}
|
16
|
+
|
17
|
+
export function print(options: PrintOptionsType): Promise<any>;
|
18
|
+
|
19
|
+
export function selectPrinter(options: SelectPrinterOptionsType): Promise<SelectPrinterReturnType>;
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# react-native-print Windows Implementation
|
2
|
+
|
3
|
+
## Module Installation
|
4
|
+
You can either use autolinking on react-native-windows 0.63 and later or manually link the module on earlier realeases.
|
5
|
+
|
6
|
+
## Automatic install with autolinking on RNW >= 0.63
|
7
|
+
react-native-print supports autolinking. Just call: `npm i react-native-print --save`
|
8
|
+
|
9
|
+
## Manual installation on RNW >= 0.62
|
10
|
+
1. `npm install react-native-print --save`
|
11
|
+
2. Open your solution in Visual Studio 2019 (eg. `windows\yourapp.sln`)
|
12
|
+
3. Right-click Solution icon in Solution Explorer > Add > Existing Project...
|
13
|
+
4. Add `node_modules\react-native-print\windows\RNPrint\RNPrint.vcxproj`
|
14
|
+
5. Right-click main application project > Add > Reference...
|
15
|
+
6. Select `RNPrint` in Solution Projects
|
16
|
+
7. In app `pch.h` add `#include "winrt/RNPrint.h"`
|
17
|
+
8. In `App.cpp` add `PackageProviders().Append(winrt::RNPrint::ReactPackageProvider());` before `InitializeComponent();`
|
18
|
+
|
19
|
+
## Windows print canvas
|
20
|
+
|
21
|
+
On Windows, `react-native-print` needs an element in the visual tree to add the printable pages to.
|
22
|
+
It will look for a XAML `Canvas` named `RNPrintCanvas` and use it.
|
23
|
+
This needs to be added to the XAML tree of the screens where `react-native-print` is used.
|
24
|
+
|
25
|
+
As an example, in `windows/myapp/MainPage.xaml` from the `react-native-windows` app template this can be done by adding a XAML `Grid` with an invisible `Canvas` alongside the `ReactRootView`. Change `windows/myapp/MainPage.xaml` from:
|
26
|
+
```xaml
|
27
|
+
<Page
|
28
|
+
...
|
29
|
+
>
|
30
|
+
<react:ReactRootView
|
31
|
+
x:Name="ReactRootView"
|
32
|
+
...
|
33
|
+
/>
|
34
|
+
</Page>
|
35
|
+
```
|
36
|
+
to
|
37
|
+
```xaml
|
38
|
+
<Page
|
39
|
+
...
|
40
|
+
>
|
41
|
+
<Grid>
|
42
|
+
<Canvas x:Name="RNPrintCanvas" Opacity="0" />
|
43
|
+
<react:ReactRootView
|
44
|
+
x:Name="ReactRootView"
|
45
|
+
...
|
46
|
+
/>
|
47
|
+
</Grid>
|
48
|
+
</Page>
|
49
|
+
```
|
50
|
+
|
51
|
+
## Module development
|
52
|
+
|
53
|
+
If you want to contribute to this module Windows implementation, first you must install the [Windows Development Dependencies](https://aka.ms/rnw-deps).
|
54
|
+
|
55
|
+
You must temporarily install the `react-native-windows` package. Versions of `react-native-windows` and `react-native` must match, e.g. if the module uses `react-native@0.62`, install `npm i react-native-windows@^0.62 --dev`.
|
56
|
+
|
57
|
+
Now, you will be able to open corresponding `RNPrint...sln` file, e.g. `RNPrint62.sln` for `react-native-windows@0.62`.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
3
|
+
<ImportGroup Label="PropertySheets" />
|
4
|
+
<PropertyGroup Label="UserMacros" />
|
5
|
+
<!--
|
6
|
+
To customize common C++/WinRT project properties:
|
7
|
+
* right-click the project node
|
8
|
+
* expand the Common Properties item
|
9
|
+
* select the C++/WinRT property page
|
10
|
+
|
11
|
+
For more advanced scenarios, and complete documentation, please see:
|
12
|
+
https://github.com/Microsoft/xlang/tree/master/src/package/cppwinrt/nuget
|
13
|
+
-->
|
14
|
+
<PropertyGroup />
|
15
|
+
<ItemDefinitionGroup />
|
16
|
+
</Project>
|
@@ -0,0 +1,375 @@
|
|
1
|
+
#include "pch.h"
|
2
|
+
#include "RNPrint.h"
|
3
|
+
|
4
|
+
namespace winrt::RNPrint
|
5
|
+
{
|
6
|
+
// Searches for the RNPrintCanvas element in the visual tree.
|
7
|
+
Canvas RNPrint::searchForPrintCanvas(xaml::DependencyObject startNode)
|
8
|
+
{
|
9
|
+
const int count = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetChildrenCount(startNode);
|
10
|
+
Canvas result = nullptr;
|
11
|
+
for (int i = 0; i < count; i++)
|
12
|
+
{
|
13
|
+
xaml::DependencyObject current = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetChild(startNode, i);
|
14
|
+
Canvas temp = current.try_as<Canvas>();
|
15
|
+
if (temp)
|
16
|
+
{
|
17
|
+
std::string tempName = winrt::to_string(temp.Name());
|
18
|
+
if (tempName == RNPrintCanvasName)
|
19
|
+
{
|
20
|
+
return temp;
|
21
|
+
}
|
22
|
+
} else
|
23
|
+
{
|
24
|
+
result = searchForPrintCanvas(current);
|
25
|
+
if (result)
|
26
|
+
{
|
27
|
+
break;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
return result;
|
32
|
+
}
|
33
|
+
|
34
|
+
// cleans up local variables and XAML elements.
|
35
|
+
void RNPrint::cleanUp()
|
36
|
+
{
|
37
|
+
reactContext.UIDispatcher().Post([=]()
|
38
|
+
{
|
39
|
+
auto printMan = PrintManager::GetForCurrentView();
|
40
|
+
{
|
41
|
+
std::lock_guard<std::mutex> guard(printSync);
|
42
|
+
servingPrintRequest = false;
|
43
|
+
if (printToken)
|
44
|
+
{
|
45
|
+
printMan.PrintTaskRequested(printToken);
|
46
|
+
}
|
47
|
+
printToken = {};
|
48
|
+
pageCollection.clear();
|
49
|
+
if (printCanvas)
|
50
|
+
{
|
51
|
+
printCanvas.Children().Clear();
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
58
|
+
// Generates a XAML page asynchronously.
|
59
|
+
concurrency::task<std::optional<xaml::UIElement>> RNPrint::GeneratePageAsync(
|
60
|
+
PdfDocument pdfDocument,
|
61
|
+
int pageNumber,
|
62
|
+
PrintPageDescription pageDescription)
|
63
|
+
{
|
64
|
+
Image pageImage;
|
65
|
+
PdfPage pdfPage = pdfDocument.GetPage(pageNumber - 1);
|
66
|
+
BitmapImage pageSource;
|
67
|
+
pageImage.Source(pageSource);
|
68
|
+
InMemoryRandomAccessStream pageStream;
|
69
|
+
Canvas page;
|
70
|
+
page.Width(pageDescription.PageSize.Width);
|
71
|
+
page.Height(pageDescription.PageSize.Height);
|
72
|
+
|
73
|
+
const float leftMargin = pageDescription.ImageableRect.X;
|
74
|
+
const float topMargin = pageDescription.ImageableRect.Y;
|
75
|
+
const float printableWidth = pageDescription.ImageableRect.Width;
|
76
|
+
const float printableHeight = pageDescription.ImageableRect.Height;
|
77
|
+
|
78
|
+
Canvas printableAreaCanvas;
|
79
|
+
printableAreaCanvas.Width(printableWidth);
|
80
|
+
printableAreaCanvas.Height(printableWidth);
|
81
|
+
|
82
|
+
auto pdfContentDimensions = pdfPage.Dimensions().MediaBox();
|
83
|
+
float pdfContentWidth = pdfContentDimensions.Width;
|
84
|
+
float pdfContentHeight = pdfContentDimensions.Height;
|
85
|
+
|
86
|
+
//Scale to Fit logic
|
87
|
+
const float scale = min(printableHeight / pdfContentHeight, printableWidth / pdfContentWidth);
|
88
|
+
pdfContentWidth *= scale;
|
89
|
+
pdfContentHeight *= scale;
|
90
|
+
Canvas::SetLeft(printableAreaCanvas, (double)leftMargin + (printableWidth - pdfContentWidth) / 2);
|
91
|
+
Canvas::SetTop(printableAreaCanvas, (double)topMargin + (printableHeight - pdfContentHeight) / 2);
|
92
|
+
|
93
|
+
auto renderOptions = PdfPageRenderOptions();
|
94
|
+
renderOptions.SourceRect(pdfContentDimensions);
|
95
|
+
renderOptions.DestinationWidth(pdfContentWidth);
|
96
|
+
renderOptions.DestinationHeight(pdfContentHeight);
|
97
|
+
|
98
|
+
printableAreaCanvas.Children().Append(pageImage);
|
99
|
+
page.Children().Append(printableAreaCanvas);
|
100
|
+
|
101
|
+
// Can't block or co_await on this Delegate.
|
102
|
+
auto renderToStreamTask = pdfPage.RenderToStreamAsync(pageStream, renderOptions);
|
103
|
+
return concurrency::create_task([=]
|
104
|
+
{
|
105
|
+
renderToStreamTask.get();
|
106
|
+
reactContext.UIDispatcher().Post([=]()
|
107
|
+
{
|
108
|
+
// Needs to be run on UI thread.
|
109
|
+
pageSource.SetSource(pageStream);
|
110
|
+
});
|
111
|
+
}).then([=]() -> std::optional<xaml::UIElement>
|
112
|
+
{
|
113
|
+
return page;
|
114
|
+
});
|
115
|
+
}
|
116
|
+
|
117
|
+
// Asynchronously loads the PDF document, registers print callbacks and fires the print UI.
|
118
|
+
IAsyncAction RNPrint::PrintAsyncHelper(
|
119
|
+
RNPrintOptions options,
|
120
|
+
ReactPromise<JSValue> promise) noexcept
|
121
|
+
{
|
122
|
+
auto capturedPromise = promise;
|
123
|
+
auto capturedOptions = options;
|
124
|
+
|
125
|
+
|
126
|
+
if ((!capturedOptions.html && !capturedOptions.filePath) || (capturedOptions.html && capturedOptions.filePath))
|
127
|
+
{
|
128
|
+
cleanUp();
|
129
|
+
capturedPromise.Reject("Must provide either 'html' or 'filePath'. Both are either missing or passed together.");
|
130
|
+
co_return;
|
131
|
+
}
|
132
|
+
|
133
|
+
if (capturedOptions.html)
|
134
|
+
{
|
135
|
+
cleanUp();
|
136
|
+
capturedPromise.Reject("Printing HTML not supported");
|
137
|
+
co_return;
|
138
|
+
} else
|
139
|
+
{
|
140
|
+
// Pdf print instructions.
|
141
|
+
winrt::Windows::Foundation::Uri uri(winrt::to_hstring(*capturedOptions.filePath));
|
142
|
+
|
143
|
+
bool isValidURL = !uri.Host().empty() && (winrt::to_string(uri.SchemeName()) == "http" || winrt::to_string(uri.SchemeName()) == "https");
|
144
|
+
PdfDocument pdfDocument = nullptr;
|
145
|
+
|
146
|
+
if (isValidURL)
|
147
|
+
{
|
148
|
+
// Should be a valid URL.
|
149
|
+
auto httpClient = HttpClient();
|
150
|
+
auto httpResponseMessage = co_await httpClient.GetAsync(uri);
|
151
|
+
auto memoryStream = InMemoryRandomAccessStream();
|
152
|
+
co_await httpResponseMessage.Content().WriteToStreamAsync(memoryStream.GetOutputStreamAt(0));
|
153
|
+
pdfDocument = co_await PdfDocument::LoadFromStreamAsync(memoryStream);
|
154
|
+
} else
|
155
|
+
{
|
156
|
+
// Not a valid URL, try to open as a file.
|
157
|
+
StorageFile pdfFile = nullptr;
|
158
|
+
pdfFile = co_await StorageFile::GetFileFromPathAsync(winrt::to_hstring(*capturedOptions.filePath));
|
159
|
+
pdfDocument = co_await PdfDocument::LoadFromFileAsync(pdfFile);
|
160
|
+
}
|
161
|
+
if (pdfDocument == nullptr)
|
162
|
+
{
|
163
|
+
cleanUp();
|
164
|
+
capturedPromise.Reject("Couldn't open the PDF file.");
|
165
|
+
co_return;
|
166
|
+
} else
|
167
|
+
{
|
168
|
+
auto printMan = PrintManager::GetForCurrentView();
|
169
|
+
printDoc = PrintDocument();
|
170
|
+
printDocSource = printDoc.DocumentSource();
|
171
|
+
|
172
|
+
printDoc.Paginate(
|
173
|
+
[=](auto const& sender, PaginateEventArgs const& args)
|
174
|
+
{
|
175
|
+
{
|
176
|
+
// Clears the XAML pages.
|
177
|
+
std::lock_guard<std::mutex> guard(printSync);
|
178
|
+
pageCollection.clear();
|
179
|
+
printCanvas.Children().Clear();
|
180
|
+
}
|
181
|
+
auto printTaskOptions = args.PrintTaskOptions();
|
182
|
+
printPageDescr = printTaskOptions.GetPageDescription(0);
|
183
|
+
numberOfPages = pdfDocument.PageCount();
|
184
|
+
sender.as<PrintDocument>().SetPreviewPageCount(numberOfPages, PreviewPageCountType::Final);
|
185
|
+
}
|
186
|
+
);
|
187
|
+
|
188
|
+
printDoc.GetPreviewPage(
|
189
|
+
[=](auto const& sender, GetPreviewPageEventArgs const& args) -> void
|
190
|
+
{
|
191
|
+
auto printDocArg = sender.as<PrintDocument>();
|
192
|
+
const int pageNumber = args.PageNumber();
|
193
|
+
GeneratePageAsync(pdfDocument, pageNumber, printPageDescr).then([=](std::optional<xaml::UIElement> generatedPage)
|
194
|
+
{
|
195
|
+
reactContext.UIDispatcher().Post([=]()
|
196
|
+
{
|
197
|
+
{
|
198
|
+
std::lock_guard<std::mutex> guard(printSync);
|
199
|
+
printCanvas.Children().Append(*generatedPage);
|
200
|
+
printCanvas.InvalidateMeasure();
|
201
|
+
printCanvas.UpdateLayout();
|
202
|
+
}
|
203
|
+
printDocArg.SetPreviewPage(pageNumber, *generatedPage);
|
204
|
+
});
|
205
|
+
}
|
206
|
+
);
|
207
|
+
}
|
208
|
+
);
|
209
|
+
|
210
|
+
printDoc.AddPages(
|
211
|
+
[=](auto const& sender, AddPagesEventArgs const& args)
|
212
|
+
{
|
213
|
+
PrintDocument printDoc = sender.as<PrintDocument>();
|
214
|
+
std::vector<concurrency::task<void>> createPageTasks;
|
215
|
+
|
216
|
+
// Generate tasks for each page that needs to be sent to the printer.
|
217
|
+
for (int i = 0; i < numberOfPages; i++)
|
218
|
+
{
|
219
|
+
createPageTasks.push_back(GeneratePageAsync(pdfDocument, i + 1, printPageDescr).then([=](std::optional<xaml::UIElement> generatedPage)
|
220
|
+
{
|
221
|
+
pageCollection[i] = *generatedPage;
|
222
|
+
}
|
223
|
+
));
|
224
|
+
}
|
225
|
+
|
226
|
+
// After all pages have been generated, submit them for printing.
|
227
|
+
concurrency::when_all(createPageTasks.begin(), createPageTasks.end()).then(
|
228
|
+
[=]()
|
229
|
+
{
|
230
|
+
reactContext.UIDispatcher().Post([=]()
|
231
|
+
{
|
232
|
+
std::for_each(pageCollection.begin(), pageCollection.end(),
|
233
|
+
[=](auto keyValue)
|
234
|
+
{
|
235
|
+
|
236
|
+
printDoc.AddPage(*(keyValue.second));
|
237
|
+
}
|
238
|
+
);
|
239
|
+
printDoc.AddPagesComplete();
|
240
|
+
}
|
241
|
+
);
|
242
|
+
}
|
243
|
+
);
|
244
|
+
|
245
|
+
});
|
246
|
+
|
247
|
+
printToken = printMan.PrintTaskRequested(
|
248
|
+
[=](PrintManager const& sender, PrintTaskRequestedEventArgs const& args)
|
249
|
+
{
|
250
|
+
auto printTask = args.Request().CreatePrintTask(winrt::to_hstring(capturedOptions.jobName),
|
251
|
+
[=](PrintTaskSourceRequestedArgs const& args)
|
252
|
+
{
|
253
|
+
args.SetSource(printDocSource);
|
254
|
+
}
|
255
|
+
);
|
256
|
+
if (options.isLandscape)
|
257
|
+
{
|
258
|
+
printTask.Options().Orientation(PrintOrientation::Landscape);
|
259
|
+
} else
|
260
|
+
{
|
261
|
+
printTask.Options().Orientation(PrintOrientation::Portrait);
|
262
|
+
}
|
263
|
+
|
264
|
+
printTask.Completed([=](PrintTask const& sender, PrintTaskCompletedEventArgs const& args)
|
265
|
+
{
|
266
|
+
cleanUp();
|
267
|
+
switch (args.Completion())
|
268
|
+
{
|
269
|
+
case PrintTaskCompletion::Failed:
|
270
|
+
capturedPromise.Reject("Failed to print.");
|
271
|
+
break;
|
272
|
+
case PrintTaskCompletion::Abandoned:
|
273
|
+
capturedPromise.Reject("Printing Abandoned");
|
274
|
+
break;
|
275
|
+
default:
|
276
|
+
capturedPromise.Resolve(options.jobName);
|
277
|
+
break;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
);
|
281
|
+
}
|
282
|
+
);
|
283
|
+
|
284
|
+
co_await PrintManager::ShowPrintUIAsync();
|
285
|
+
}
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
void RNPrint::Print(JSValueObject&& options, ReactPromise<JSValue> promise) noexcept
|
290
|
+
{
|
291
|
+
bool ok_to_go_ahead = true;
|
292
|
+
{
|
293
|
+
// Only support one print request at a time.
|
294
|
+
std::lock_guard<std::mutex> guard(printSync);
|
295
|
+
if (servingPrintRequest)
|
296
|
+
{
|
297
|
+
ok_to_go_ahead = false;
|
298
|
+
} else
|
299
|
+
{
|
300
|
+
servingPrintRequest = true;
|
301
|
+
}
|
302
|
+
}
|
303
|
+
if (!ok_to_go_ahead)
|
304
|
+
{
|
305
|
+
promise.Reject("Another print request is already being served.");
|
306
|
+
return;
|
307
|
+
}
|
308
|
+
|
309
|
+
RNPrintOptions printOptions;
|
310
|
+
|
311
|
+
if (options.find("html") != options.end())
|
312
|
+
{
|
313
|
+
printOptions.html = options["html"].AsString();
|
314
|
+
}
|
315
|
+
|
316
|
+
std::optional<std::string> filePath = std::nullopt;
|
317
|
+
if (options.find("filePath") != options.end())
|
318
|
+
{
|
319
|
+
printOptions.filePath = options["filePath"].AsString();
|
320
|
+
}
|
321
|
+
|
322
|
+
printOptions.isLandscape = (options.find("isLandscape") != options.end() ? options["isLandscape"].AsBoolean() : false);
|
323
|
+
printOptions.jobName = (options.find("jobName") != options.end() ? options["jobName"].AsString() : defaultJobName);
|
324
|
+
reactContext.UIDispatcher().Post([=]()
|
325
|
+
{
|
326
|
+
xaml::FrameworkElement root{ nullptr };
|
327
|
+
|
328
|
+
auto window = xaml::Window::Current();
|
329
|
+
|
330
|
+
if (window != nullptr)
|
331
|
+
{
|
332
|
+
root = window.Content().try_as<xaml::FrameworkElement>();
|
333
|
+
} else
|
334
|
+
{
|
335
|
+
if (auto xamlRoot = React::XamlUIService::GetXamlRoot(reactContext.Properties().Handle()))
|
336
|
+
{
|
337
|
+
root = xamlRoot.Content().try_as<xaml::FrameworkElement>();
|
338
|
+
}
|
339
|
+
}
|
340
|
+
|
341
|
+
if (!root)
|
342
|
+
{
|
343
|
+
cleanUp();
|
344
|
+
promise.Reject("A valid XAML root was not found.");
|
345
|
+
return;
|
346
|
+
}
|
347
|
+
|
348
|
+
printCanvas = searchForPrintCanvas(root);
|
349
|
+
|
350
|
+
if (!printCanvas)
|
351
|
+
{
|
352
|
+
cleanUp();
|
353
|
+
promise.Reject("The XAML Canvas named \"RNPrintCanvas\" was not found.");
|
354
|
+
return;
|
355
|
+
}
|
356
|
+
|
357
|
+
auto asyncOp = PrintAsyncHelper(printOptions, promise);
|
358
|
+
asyncOp.Completed([=](auto action, auto status)
|
359
|
+
{
|
360
|
+
// Here we handle any unhandled exceptions thrown during the
|
361
|
+
// asynchronous call by rejecting the promise with the error code
|
362
|
+
if (status == winrt::Windows::Foundation::AsyncStatus::Error)
|
363
|
+
{
|
364
|
+
cleanUp();
|
365
|
+
std::stringstream errorCode;
|
366
|
+
errorCode << "0x" << std::hex << action.ErrorCode() << std::dec << std::endl;
|
367
|
+
|
368
|
+
auto error = winrt::Microsoft::ReactNative::ReactError();
|
369
|
+
error.Message = "HRESULT " + errorCode.str() + ": " + std::system_category().message(action.ErrorCode());
|
370
|
+
promise.Reject(error);
|
371
|
+
}
|
372
|
+
});
|
373
|
+
});
|
374
|
+
}
|
375
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include "pch.h"
|
4
|
+
#include "NativeModules.h"
|
5
|
+
|
6
|
+
#include <functional>
|
7
|
+
#include <sstream>
|
8
|
+
#include <mutex>
|
9
|
+
#include <string_view>
|
10
|
+
|
11
|
+
#include "JSValue.h"
|
12
|
+
|
13
|
+
using namespace winrt::Microsoft::ReactNative;
|
14
|
+
using namespace winrt::Windows::Data::Pdf;
|
15
|
+
using namespace winrt::Windows::Foundation;
|
16
|
+
using namespace winrt::Windows::Storage;
|
17
|
+
using namespace winrt::Windows::Storage::Streams;
|
18
|
+
using namespace winrt::Windows::Web::Http;
|
19
|
+
using namespace winrt::Windows::Graphics::Printing;
|
20
|
+
using namespace winrt::Windows::Graphics::Printing::OptionDetails;
|
21
|
+
using namespace winrt::Windows::UI::Xaml::Printing;
|
22
|
+
using namespace winrt::Windows::UI::Xaml::Controls;
|
23
|
+
using namespace winrt::Windows::UI::Xaml::Media::Imaging;
|
24
|
+
using namespace winrt::Windows::UI::Xaml::Media;
|
25
|
+
|
26
|
+
namespace winrt::RNPrint
|
27
|
+
{
|
28
|
+
// Represent the options object passed to print()
|
29
|
+
struct RNPrintOptions
|
30
|
+
{
|
31
|
+
std::optional<std::string> html = std::nullopt;
|
32
|
+
std::optional<std::string> filePath = std::nullopt;
|
33
|
+
bool isLandscape = false;
|
34
|
+
std::string jobName;
|
35
|
+
};
|
36
|
+
|
37
|
+
REACT_MODULE(RNPrint);
|
38
|
+
struct RNPrint
|
39
|
+
{
|
40
|
+
inline static constexpr std::string_view Name = "RNPrint";
|
41
|
+
|
42
|
+
// Default name for the print job.
|
43
|
+
inline static constexpr std::string_view defaultJobName = "Document";
|
44
|
+
|
45
|
+
// Name for the XAML Canvas to add preview folders to.
|
46
|
+
inline static constexpr std::string_view RNPrintCanvasName = "RNPrintCanvas";
|
47
|
+
|
48
|
+
// These are needed between print PDF callbacks, so we place them in the struct.
|
49
|
+
ReactContext reactContext = nullptr;
|
50
|
+
Canvas printCanvas = nullptr; // holds reference to RNPrintCanvas in visual tree
|
51
|
+
PrintPageDescription printPageDescr;
|
52
|
+
PrintDocument printDoc = nullptr;
|
53
|
+
IPrintDocumentSource printDocSource;
|
54
|
+
int numberOfPages = 0;
|
55
|
+
std::map<int, std::optional<xaml::UIElement>> pageCollection; // references to the xaml pages
|
56
|
+
std::mutex printSync; // prevent race conditions when manipulating pageCollection or checking a print request is being processed
|
57
|
+
bool servingPrintRequest = false;
|
58
|
+
|
59
|
+
// Event token for print registratio.
|
60
|
+
winrt::event_token printToken;
|
61
|
+
|
62
|
+
REACT_INIT(RNPrint_Init);
|
63
|
+
void RNPrint_Init(ReactContext const& context) noexcept
|
64
|
+
{
|
65
|
+
reactContext = context;
|
66
|
+
}
|
67
|
+
|
68
|
+
// Searches for the RNPrintCanvas element in the visual tree.
|
69
|
+
Canvas searchForPrintCanvas(xaml::DependencyObject startNode);
|
70
|
+
|
71
|
+
// cleans up local variables and XAML elements.
|
72
|
+
void cleanUp();
|
73
|
+
|
74
|
+
// Generates a XAML page asynchronously.
|
75
|
+
concurrency::task<std::optional<xaml::UIElement>> GeneratePageAsync(
|
76
|
+
PdfDocument pdfDocument,
|
77
|
+
int pageNumber,
|
78
|
+
PrintPageDescription pageDescription);
|
79
|
+
|
80
|
+
// Asynchronously loads the PDF document, registers print callbacks and fires the print UI.
|
81
|
+
IAsyncAction PrintAsyncHelper(
|
82
|
+
RNPrintOptions options,
|
83
|
+
ReactPromise<JSValue> promise) noexcept;
|
84
|
+
|
85
|
+
REACT_METHOD(Print, L"print");
|
86
|
+
void Print(JSValueObject&& options, ReactPromise<JSValue> promise) noexcept;
|
87
|
+
|
88
|
+
};
|
89
|
+
}
|