@react-native-ohos/react-native-pdf 6.7.7-rc.1
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/DoubleTapView.js +125 -0
- package/LICENSE +21 -0
- package/PdfManager.js +26 -0
- package/PdfPageView.js +53 -0
- package/PdfView.js +417 -0
- package/PdfViewFlatList.js +30 -0
- package/PinchZoomView.js +125 -0
- package/README.OpenSource +11 -0
- package/README.md +13 -0
- package/fabric/RNPDFPdfNativeComponent.js +48 -0
- package/fabric/RTNPdfViewNativeComponent.ts +57 -0
- package/harmony/pdfview/BuildProfile.ets +17 -0
- package/harmony/pdfview/Index.ets +7 -0
- package/harmony/pdfview/build-profile.json5 +28 -0
- package/harmony/pdfview/consumer-rules.txt +0 -0
- package/harmony/pdfview/hvigorfile.ts +6 -0
- package/harmony/pdfview/obfuscation-rules.txt +18 -0
- package/harmony/pdfview/oh-package-lock.json5 +18 -0
- package/harmony/pdfview/oh-package.json5 +11 -0
- package/harmony/pdfview/src/main/cpp/CMakeLists.txt +8 -0
- package/harmony/pdfview/src/main/cpp/ComponentDescriptors.h +36 -0
- package/harmony/pdfview/src/main/cpp/EventEmitters.cpp +39 -0
- package/harmony/pdfview/src/main/cpp/EventEmitters.h +47 -0
- package/harmony/pdfview/src/main/cpp/PdfEventEmitRequestHandler.h +50 -0
- package/harmony/pdfview/src/main/cpp/PdfViewJSIBinder.h +71 -0
- package/harmony/pdfview/src/main/cpp/PdfViewPackage.h +55 -0
- package/harmony/pdfview/src/main/cpp/Props.cpp +58 -0
- package/harmony/pdfview/src/main/cpp/Props.h +61 -0
- package/harmony/pdfview/src/main/cpp/RTNPdfViewSpecsJSI-generated.cpp +34 -0
- package/harmony/pdfview/src/main/cpp/RTNPdfViewSpecsJSI.h +36 -0
- package/harmony/pdfview/src/main/cpp/ShadowNodes.cpp +33 -0
- package/harmony/pdfview/src/main/cpp/ShadowNodes.h +47 -0
- package/harmony/pdfview/src/main/cpp/States.cpp +33 -0
- package/harmony/pdfview/src/main/cpp/States.h +52 -0
- package/harmony/pdfview/src/main/ets/Logger.ets +64 -0
- package/harmony/pdfview/src/main/ets/components/mainpage/RTNPdfView.ets +513 -0
- package/harmony/pdfview/src/main/ets/components/mainpage/types.ts +15 -0
- package/harmony/pdfview/src/main/module.json5 +11 -0
- package/harmony/pdfview/src/main/resources/base/element/string.json +8 -0
- package/harmony/pdfview/src/main/resources/en_US/element/string.json +8 -0
- package/harmony/pdfview/src/main/resources/zh_CN/element/string.json +8 -0
- package/harmony/pdfview/src/test/List.test.ets +29 -0
- package/harmony/pdfview/src/test/LocalUnit.test.ets +57 -0
- package/harmony/pdfview.har +0 -0
- package/index.d.ts +72 -0
- package/index.js +491 -0
- package/index.js.flow +67 -0
- package/package.json +61 -0
- package/windows/RCTPdf/PropertySheet.props +16 -0
- package/windows/RCTPdf/RCTPdf.def +3 -0
- package/windows/RCTPdf/RCTPdf.vcxproj +180 -0
- package/windows/RCTPdf/RCTPdf.vcxproj.filters +38 -0
- package/windows/RCTPdf/RCTPdfControl.cpp +667 -0
- package/windows/RCTPdf/RCTPdfControl.h +119 -0
- package/windows/RCTPdf/RCTPdfControl.idl +10 -0
- package/windows/RCTPdf/RCTPdfControl.xaml +33 -0
- package/windows/RCTPdf/RCTPdfViewManager.cpp +69 -0
- package/windows/RCTPdf/RCTPdfViewManager.h +51 -0
- package/windows/RCTPdf/ReactPackageProvider.cpp +15 -0
- package/windows/RCTPdf/ReactPackageProvider.h +16 -0
- package/windows/RCTPdf/ReactPackageProvider.idl +9 -0
- package/windows/RCTPdf/packages.config +4 -0
- package/windows/RCTPdf/pch.cpp +1 -0
- package/windows/RCTPdf/pch.h +31 -0
- package/windows/README.md +21 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
#include "pch.h"
|
|
2
|
+
#include "RCTPdfControl.h"
|
|
3
|
+
#if __has_include("RCTPdfControl.g.cpp")
|
|
4
|
+
#include "RCTPdfControl.g.cpp"
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
using namespace winrt;
|
|
8
|
+
using namespace Windows::UI::Xaml;
|
|
9
|
+
using namespace Microsoft::ReactNative;
|
|
10
|
+
using namespace Windows::Data::Json;
|
|
11
|
+
using namespace Windows::Data::Pdf;
|
|
12
|
+
using namespace Windows::Foundation;
|
|
13
|
+
using namespace Windows::Storage;
|
|
14
|
+
using namespace Windows::Storage::Streams;
|
|
15
|
+
using namespace Windows::Storage::Pickers;
|
|
16
|
+
using namespace Windows::UI;
|
|
17
|
+
using namespace Windows::UI::Core;
|
|
18
|
+
using namespace Windows::UI::Popups;
|
|
19
|
+
using namespace Windows::UI::Xaml;
|
|
20
|
+
using namespace Windows::UI::Xaml::Controls;
|
|
21
|
+
using namespace Windows::UI::Xaml::Input;
|
|
22
|
+
using namespace Windows::UI::Xaml::Media;
|
|
23
|
+
using namespace Windows::UI::Xaml::Media::Imaging;
|
|
24
|
+
|
|
25
|
+
namespace winrt::RCTPdf::implementation
|
|
26
|
+
{
|
|
27
|
+
PDFPageInfo::PDFPageInfo(winrt::Windows::UI::Xaml::Controls::Image image, winrt::Windows::Data::Pdf::PdfPage page, double imageScale, double renderScale) :
|
|
28
|
+
image(image), page(page), imageScale(imageScale), renderScale(renderScale), scaledTopOffset(0), scaledLeftOffset(0) {
|
|
29
|
+
auto dims = page.Size();
|
|
30
|
+
height = (unsigned)dims.Height;
|
|
31
|
+
width = (unsigned)dims.Width;
|
|
32
|
+
scaledHeight = (unsigned)(height * imageScale);
|
|
33
|
+
scaledWidth = (unsigned)(width * imageScale);
|
|
34
|
+
}
|
|
35
|
+
PDFPageInfo::PDFPageInfo(const PDFPageInfo& rhs) :
|
|
36
|
+
height(rhs.height), width(rhs.width), scaledHeight(rhs.scaledHeight), scaledWidth(rhs.scaledWidth),
|
|
37
|
+
scaledTopOffset(rhs.scaledTopOffset), scaledLeftOffset(rhs.scaledTopOffset), imageScale(rhs.imageScale),
|
|
38
|
+
renderScale((double)rhs.renderScale), image(rhs.image), page(rhs.page)
|
|
39
|
+
{ }
|
|
40
|
+
PDFPageInfo::PDFPageInfo(PDFPageInfo&& rhs) :
|
|
41
|
+
height(rhs.height), width(rhs.width), scaledHeight(rhs.scaledHeight), scaledWidth(rhs.scaledWidth),
|
|
42
|
+
scaledTopOffset(rhs.scaledTopOffset), scaledLeftOffset(rhs.scaledTopOffset), imageScale(rhs.imageScale),
|
|
43
|
+
renderScale((double)rhs.renderScale), image(std::move(rhs.image)), page(std::move(rhs.page))
|
|
44
|
+
{ }
|
|
45
|
+
unsigned PDFPageInfo::pageVisiblePixels(bool horizontal, double viewportStart, double viewportEnd) const {
|
|
46
|
+
if (viewportEnd < viewportStart)
|
|
47
|
+
std::swap(viewportStart, viewportEnd);
|
|
48
|
+
auto pageStart = horizontal ? scaledLeftOffset : scaledTopOffset;
|
|
49
|
+
auto pageSize = PDFPageInfo::pageSize(horizontal);
|
|
50
|
+
auto pageEnd = pageStart + pageSize;
|
|
51
|
+
auto uViewportStart = (unsigned)viewportStart;
|
|
52
|
+
auto uViewportEnd = (unsigned)viewportEnd;
|
|
53
|
+
if (pageStart >= uViewportStart && pageStart <= uViewportEnd) { // we see the top edge
|
|
54
|
+
return (std::min)(pageEnd, uViewportEnd) - pageStart;
|
|
55
|
+
}
|
|
56
|
+
if (pageEnd >= uViewportStart && pageEnd <= uViewportEnd) { // we see the bottom edge
|
|
57
|
+
return pageEnd - (std::max)(pageStart, uViewportStart);
|
|
58
|
+
}
|
|
59
|
+
if (pageStart <= uViewportStart && pageEnd >= uViewportEnd) {// we see the entire page
|
|
60
|
+
return uViewportEnd - uViewportStart;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
unsigned PDFPageInfo::pageSize(bool horizontal) const {
|
|
65
|
+
return horizontal ? scaledWidth : scaledHeight;
|
|
66
|
+
}
|
|
67
|
+
bool PDFPageInfo::needsRender() const {
|
|
68
|
+
double currentRenderScale = renderScale;
|
|
69
|
+
return currentRenderScale < imageScale || currentRenderScale > imageScale * m_downscaleTreshold;
|
|
70
|
+
}
|
|
71
|
+
winrt::Windows::Foundation::IAsyncAction PDFPageInfo::render() {
|
|
72
|
+
return render(imageScale);
|
|
73
|
+
}
|
|
74
|
+
winrt::Windows::Foundation::IAsyncAction PDFPageInfo::render(double useScale) {
|
|
75
|
+
double currentRenderScale;
|
|
76
|
+
while (true) {
|
|
77
|
+
currentRenderScale = renderScale;
|
|
78
|
+
if (!(currentRenderScale < imageScale || currentRenderScale > imageScale * m_downscaleTreshold))
|
|
79
|
+
co_return;
|
|
80
|
+
if (renderScale.compare_exchange_weak(currentRenderScale, useScale))
|
|
81
|
+
break;
|
|
82
|
+
if (renderScale > useScale)
|
|
83
|
+
co_return;
|
|
84
|
+
}
|
|
85
|
+
PdfPageRenderOptions renderOptions;
|
|
86
|
+
auto dims = page.Size();
|
|
87
|
+
renderOptions.DestinationHeight(static_cast<uint32_t>(dims.Height * useScale));
|
|
88
|
+
renderOptions.DestinationWidth(static_cast<uint32_t>(dims.Width * useScale));
|
|
89
|
+
InMemoryRandomAccessStream stream;
|
|
90
|
+
co_await page.RenderToStreamAsync(stream, renderOptions);
|
|
91
|
+
BitmapImage bitmap;
|
|
92
|
+
co_await bitmap.SetSourceAsync(stream);
|
|
93
|
+
if (renderScale == useScale)
|
|
94
|
+
image.Source(bitmap);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
RCTPdfControl::RCTPdfControl(IReactContext const& reactContext) : m_reactContext(reactContext) {
|
|
98
|
+
InitializeComponent();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microsoft::ReactNative::ViewManagerPropertyType> RCTPdfControl::NativeProps() noexcept {
|
|
102
|
+
auto nativeProps = winrt::single_threaded_map<hstring, ViewManagerPropertyType>();
|
|
103
|
+
nativeProps.Insert(L"path", ViewManagerPropertyType::String);
|
|
104
|
+
nativeProps.Insert(L"page", ViewManagerPropertyType::Number);
|
|
105
|
+
nativeProps.Insert(L"scale", ViewManagerPropertyType::Number);
|
|
106
|
+
nativeProps.Insert(L"minScale", ViewManagerPropertyType::Number);
|
|
107
|
+
nativeProps.Insert(L"maxScale", ViewManagerPropertyType::Number);
|
|
108
|
+
nativeProps.Insert(L"horizontal", ViewManagerPropertyType::Boolean);
|
|
109
|
+
nativeProps.Insert(L"fitWidth", ViewManagerPropertyType::Boolean);
|
|
110
|
+
nativeProps.Insert(L"fitPolicy", ViewManagerPropertyType::Number);
|
|
111
|
+
nativeProps.Insert(L"spacing", ViewManagerPropertyType::Number);
|
|
112
|
+
nativeProps.Insert(L"password", ViewManagerPropertyType::String);
|
|
113
|
+
nativeProps.Insert(L"background", ViewManagerPropertyType::Color);
|
|
114
|
+
nativeProps.Insert(L"enablePaging", ViewManagerPropertyType::Boolean);
|
|
115
|
+
nativeProps.Insert(L"enableRTL", ViewManagerPropertyType::Boolean);
|
|
116
|
+
nativeProps.Insert(L"singlePage", ViewManagerPropertyType::Boolean);
|
|
117
|
+
|
|
118
|
+
return nativeProps.GetView();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
void RCTPdfControl::UpdateProperties(winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept {
|
|
122
|
+
const JSValueObject& propertyMap = JSValue::ReadObjectFrom(propertyMapReader);
|
|
123
|
+
std::optional<std::string> pdfURI;
|
|
124
|
+
std::optional<std::string> pdfPassword;
|
|
125
|
+
std::optional<int> setPage;
|
|
126
|
+
std::optional<double> minScale, maxScale, scale;
|
|
127
|
+
std::optional<bool> horizontal;
|
|
128
|
+
std::optional<bool> fitWidth;
|
|
129
|
+
std::optional<int> fitPolicy;
|
|
130
|
+
std::optional<int> spacing;
|
|
131
|
+
std::optional<bool> reverse;
|
|
132
|
+
std::optional<bool> singlePage;
|
|
133
|
+
std::optional<bool> enablePaging;
|
|
134
|
+
for (auto const& pair : propertyMap) {
|
|
135
|
+
auto const& propertyName = pair.first;
|
|
136
|
+
auto const& propertyValue = pair.second;
|
|
137
|
+
if (propertyName == "path") {
|
|
138
|
+
pdfURI = propertyValue != nullptr ? propertyValue.AsString() : "";
|
|
139
|
+
}
|
|
140
|
+
else if (propertyName == "password") {
|
|
141
|
+
pdfPassword = propertyValue != nullptr ? propertyValue.AsString() : "";
|
|
142
|
+
}
|
|
143
|
+
else if (propertyName == "page" && propertyValue != nullptr) {
|
|
144
|
+
setPage = propertyValue.AsInt32() - 1;
|
|
145
|
+
}
|
|
146
|
+
else if (propertyName == "scale" && propertyValue != nullptr) {
|
|
147
|
+
scale = propertyValue.AsDouble();
|
|
148
|
+
}
|
|
149
|
+
else if (propertyName == "minScale" && propertyValue != nullptr) {
|
|
150
|
+
minScale = propertyValue.AsDouble();
|
|
151
|
+
}
|
|
152
|
+
else if (propertyName == "maxScale" && propertyValue != nullptr) {
|
|
153
|
+
maxScale = propertyValue.AsDouble();
|
|
154
|
+
}
|
|
155
|
+
else if (propertyName == "horizontal" && propertyValue != nullptr) {
|
|
156
|
+
horizontal = propertyValue.AsBoolean();
|
|
157
|
+
}
|
|
158
|
+
else if (propertyName == "enablePaging" && propertyValue != nullptr) {
|
|
159
|
+
enablePaging = propertyValue.AsBoolean();
|
|
160
|
+
}
|
|
161
|
+
else if (propertyName == "fitWidth" && propertyValue != nullptr) {
|
|
162
|
+
fitWidth = propertyValue.AsBoolean();
|
|
163
|
+
}
|
|
164
|
+
else if (propertyName == "fitPolicy" && propertyValue != nullptr) {
|
|
165
|
+
fitPolicy = propertyValue.AsInt32();
|
|
166
|
+
}
|
|
167
|
+
else if (propertyName == "spacing" && propertyValue != nullptr) {
|
|
168
|
+
maxScale = propertyValue.AsInt32();
|
|
169
|
+
}
|
|
170
|
+
else if (propertyName == "enableRTL" && propertyValue != nullptr) {
|
|
171
|
+
reverse = propertyValue.AsBoolean();
|
|
172
|
+
}
|
|
173
|
+
else if (propertyName == "singlePage" && propertyValue != nullptr) {
|
|
174
|
+
singlePage = propertyValue.AsBoolean();
|
|
175
|
+
}
|
|
176
|
+
else if (propertyName == "backgroundColor" && propertyValue != nullptr) {
|
|
177
|
+
auto color = propertyValue.AsInt32();
|
|
178
|
+
winrt::Windows::UI::Color brushColor;
|
|
179
|
+
brushColor.A = (color >> 24) & 0xff;
|
|
180
|
+
brushColor.R = (color >> 16) & 0xff;
|
|
181
|
+
brushColor.G = (color >> 8) & 0xff;
|
|
182
|
+
brushColor.B = color & 0xff;
|
|
183
|
+
PagesContainer().Background(SolidColorBrush(brushColor));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// If we are loading a new PDF:
|
|
187
|
+
std::shared_lock lock(m_rwlock);
|
|
188
|
+
if (pdfURI && *pdfURI != m_pdfURI ||
|
|
189
|
+
pdfPassword && *pdfPassword != m_pdfPassword ||
|
|
190
|
+
(reverse && *reverse != m_reverse) ||
|
|
191
|
+
(enablePaging && *enablePaging != m_enablePaging) ||
|
|
192
|
+
(singlePage && (m_pages.empty() || *singlePage && m_pages.size() != 1 || !*singlePage && m_pages.size() == 1)) ) {
|
|
193
|
+
lock.unlock();
|
|
194
|
+
std::unique_lock write_lock(m_rwlock);
|
|
195
|
+
m_pdfURI = pdfURI.value_or("");
|
|
196
|
+
m_pdfPassword = pdfPassword.value_or("");
|
|
197
|
+
m_currentPage = setPage.value_or(0);
|
|
198
|
+
m_scale = scale.value_or(m_defualtZoom);
|
|
199
|
+
m_minScale = minScale.value_or(m_defaultMinZoom);
|
|
200
|
+
m_maxScale = maxScale.value_or(m_defaultMaxZoom);
|
|
201
|
+
m_horizontal = horizontal.value_or(false);
|
|
202
|
+
m_enablePaging = enablePaging.value_or(false);
|
|
203
|
+
int useFitPolicy = 2;
|
|
204
|
+
if (fitWidth)
|
|
205
|
+
useFitPolicy = 0;
|
|
206
|
+
if (fitPolicy)
|
|
207
|
+
useFitPolicy = *fitPolicy;
|
|
208
|
+
m_margins = spacing.value_or(m_defaultMargins);
|
|
209
|
+
m_reverse = reverse.value_or(false);
|
|
210
|
+
LoadPDF(std::move(write_lock), useFitPolicy, singlePage.value_or(false));
|
|
211
|
+
} else {
|
|
212
|
+
// If we are updating the pdf:
|
|
213
|
+
m_minScale = minScale.value_or(m_minScale);
|
|
214
|
+
m_maxScale = maxScale.value_or(m_maxScale);
|
|
215
|
+
bool needScroll = false;
|
|
216
|
+
if (horizontal && *horizontal != m_horizontal) {
|
|
217
|
+
SetOrientation(*horizontal);
|
|
218
|
+
needScroll = true;
|
|
219
|
+
}
|
|
220
|
+
if (setPage) {
|
|
221
|
+
m_currentPage = *setPage;
|
|
222
|
+
needScroll = true;
|
|
223
|
+
}
|
|
224
|
+
if ((scale && *scale != m_scale) || (spacing && *spacing != m_margins)) {
|
|
225
|
+
Rescale(scale.value_or(m_scale), spacing.value_or(m_margins), !needScroll);
|
|
226
|
+
}
|
|
227
|
+
if (needScroll) {
|
|
228
|
+
GoToPage(m_currentPage);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
winrt::Microsoft::ReactNative::ConstantProviderDelegate RCTPdfControl::ExportedCustomBubblingEventTypeConstants() noexcept {
|
|
234
|
+
return [](IJSValueWriter const& constantWriter) {
|
|
235
|
+
WriteCustomDirectEventTypeConstant(constantWriter, "Change");
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
winrt::Microsoft::ReactNative::ConstantProviderDelegate RCTPdfControl::ExportedCustomDirectEventTypeConstants() noexcept {
|
|
239
|
+
return nullptr;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
winrt::Windows::Foundation::Collections::IVectorView<winrt::hstring> RCTPdfControl::Commands() noexcept {
|
|
243
|
+
auto commands = winrt::single_threaded_vector<hstring>();
|
|
244
|
+
commands.Append(L"setPage");
|
|
245
|
+
return commands.GetView();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
void RCTPdfControl::DispatchCommand(winrt::hstring const& commandId, winrt::Microsoft::ReactNative::IJSValueReader const& commandArgsReader) noexcept {
|
|
249
|
+
auto commandArgs = JSValue::ReadArrayFrom(commandArgsReader);
|
|
250
|
+
if (commandId == L"setPage" && commandArgs.size() > 0) {
|
|
251
|
+
std::shared_lock lock(m_rwlock);
|
|
252
|
+
auto page = commandArgs[0].AsInt32() - 1;
|
|
253
|
+
GoToPage(page);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void RCTPdfControl::PagesContainer_PointerWheelChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e) {
|
|
258
|
+
winrt::Windows::System::VirtualKeyModifiers modifiers = e.KeyModifiers();
|
|
259
|
+
if ((modifiers & winrt::Windows::System::VirtualKeyModifiers::Control) != winrt::Windows::System::VirtualKeyModifiers::Control)
|
|
260
|
+
return;
|
|
261
|
+
double delta = (e.GetCurrentPoint(*this).Properties().MouseWheelDelta() / WHEEL_DELTA);
|
|
262
|
+
std::shared_lock lock(m_rwlock);
|
|
263
|
+
auto newScale = (std::max)((std::min)(m_scale * pow(m_zoomMultiplier, delta), m_maxScale), m_minScale);
|
|
264
|
+
Rescale(newScale, m_margins, true);
|
|
265
|
+
e.Handled(true);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
void RCTPdfControl::Pages_SizeChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::SizeChangedEventArgs const&) {
|
|
269
|
+
if (m_targetHorizontalOffset || m_targetVerticalOffset) {
|
|
270
|
+
auto container = PagesContainer();
|
|
271
|
+
PagesContainer().ChangeView(m_targetHorizontalOffset.value_or(container.HorizontalOffset()),
|
|
272
|
+
m_targetVerticalOffset.value_or(container.VerticalOffset()),
|
|
273
|
+
nullptr,
|
|
274
|
+
true);
|
|
275
|
+
m_targetHorizontalOffset.reset();
|
|
276
|
+
m_targetVerticalOffset.reset();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
winrt::fire_and_forget RCTPdfControl::PagesContainer_ViewChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Controls::ScrollViewerViewChangedEventArgs const& args)
|
|
281
|
+
{
|
|
282
|
+
auto lifetime = get_strong();
|
|
283
|
+
auto container = PagesContainer();
|
|
284
|
+
auto currentHorizontalOffset = container.HorizontalOffset();
|
|
285
|
+
auto currentVerticalOffset = container.VerticalOffset();
|
|
286
|
+
double offsetStart = m_horizontal ? currentHorizontalOffset : currentVerticalOffset;
|
|
287
|
+
double viewSize = m_horizontal ? container.ViewportWidth() : container.ViewportHeight();
|
|
288
|
+
double offsetEnd = offsetStart + viewSize;
|
|
289
|
+
std::shared_lock lock(m_rwlock, std::defer_lock);
|
|
290
|
+
if (args.IsIntermediate() || !lock.try_lock() || viewSize == 0 || m_pages.empty())
|
|
291
|
+
return;
|
|
292
|
+
// Go through pages until we reach a visible page
|
|
293
|
+
int page = 0;
|
|
294
|
+
double visiblePagePixels = 0;
|
|
295
|
+
for (; page < (int)m_pages.size(); ++page) {
|
|
296
|
+
visiblePagePixels = m_pages[page].pageVisiblePixels(m_horizontal, offsetStart, offsetEnd);
|
|
297
|
+
if (visiblePagePixels > 0)
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
if (page == (int)m_pages.size()) {
|
|
301
|
+
--page;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
double pagePixels = m_pages[page].pageSize(m_horizontal);
|
|
305
|
+
// #"page" is the first visible page. Check how much of the view port this page covers...
|
|
306
|
+
double viewCoveredByPage = visiblePagePixels / viewSize;
|
|
307
|
+
// ...and how much of the page is visible:
|
|
308
|
+
double pageVisiblePart = visiblePagePixels / pagePixels;
|
|
309
|
+
// If:
|
|
310
|
+
// - less than 50% of the screen is covered by the page
|
|
311
|
+
// - less than 50% of the page is visible (important if more than one page fits the screen)
|
|
312
|
+
// - there is a next page
|
|
313
|
+
// move the indicator to that page:
|
|
314
|
+
if (viewCoveredByPage < 0.5 && pageVisiblePart < 0.5 && page + 1 < (int)m_pages.size()) {
|
|
315
|
+
++page;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Render all visible pages - first the current one, then the next visible ones and one
|
|
319
|
+
// more, then the one before that might be partly visible, then one more before
|
|
320
|
+
co_await RenderVisiblePages(page);
|
|
321
|
+
if (page != m_currentPage) {
|
|
322
|
+
m_currentPage = page;
|
|
323
|
+
SignalPageChange(m_currentPage + 1, m_pages.size());
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
void RCTPdfControl::PagesContainer_Tapped(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Input::TappedRoutedEventArgs const& args) {
|
|
328
|
+
auto position = args.GetPosition(*this);
|
|
329
|
+
std::shared_lock lock(m_rwlock);
|
|
330
|
+
int scaledDoubleMargin = (int)(m_scale * m_margins * 2);
|
|
331
|
+
int page = 0;
|
|
332
|
+
int xPosition = (int)(position.X + PagesContainer().HorizontalOffset());
|
|
333
|
+
int yPosition = (int)(position.Y + PagesContainer().VerticalOffset());
|
|
334
|
+
for (; page < (int)m_pages.size(); ++page) {
|
|
335
|
+
if (m_horizontal) {
|
|
336
|
+
if (xPosition >= (int)m_pages[page].scaledLeftOffset &&
|
|
337
|
+
xPosition <= (int)(m_pages[page].scaledLeftOffset + m_pages[page].scaledWidth + scaledDoubleMargin))
|
|
338
|
+
break;
|
|
339
|
+
} else {
|
|
340
|
+
if (yPosition >= (int)m_pages[page].scaledTopOffset &&
|
|
341
|
+
yPosition <= (int)(m_pages[page].scaledTopOffset + m_pages[page].scaledHeight + scaledDoubleMargin))
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (page == (int)m_pages.size()) {
|
|
346
|
+
page = m_currentPage;
|
|
347
|
+
}
|
|
348
|
+
SignalPageTapped(page, (int)position.X, (int)position.Y);
|
|
349
|
+
PagesContainer().Focus(FocusState::Pointer);
|
|
350
|
+
args.Handled(true);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
void RCTPdfControl::PagesContainer_DoubleTapped(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& args)
|
|
355
|
+
{
|
|
356
|
+
std::shared_lock lock(m_rwlock);
|
|
357
|
+
double newScale = (std::min)(m_scale * m_zoomMultiplier, m_maxScale);
|
|
358
|
+
Rescale(newScale, m_margins, true);
|
|
359
|
+
PagesContainer().Focus(FocusState::Pointer);
|
|
360
|
+
args.Handled(true);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
void RCTPdfControl::ChangeScroll(double targetHorizontalOffset, double targetVerticalOffset) {
|
|
364
|
+
auto container = PagesContainer();
|
|
365
|
+
auto maxHorizontalOffset = container.ScrollableWidth();
|
|
366
|
+
auto maxVerticalOffset = container.ScrollableHeight();
|
|
367
|
+
// If viewport is bigger than a page, it is possible that target offset is
|
|
368
|
+
// smaller than max offset. Take that into account:
|
|
369
|
+
if (targetHorizontalOffset <= maxHorizontalOffset + container.ViewportWidth() &&
|
|
370
|
+
targetVerticalOffset <= maxVerticalOffset + container.ViewportHeight()) {
|
|
371
|
+
PagesContainer().ChangeView((std::min)(targetHorizontalOffset, maxHorizontalOffset),
|
|
372
|
+
(std::min)(targetVerticalOffset, maxVerticalOffset),
|
|
373
|
+
nullptr, true);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
m_targetHorizontalOffset = targetHorizontalOffset;
|
|
377
|
+
m_targetVerticalOffset = targetVerticalOffset;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
void RCTPdfControl::UpdatePagesInfoMarginOrScale() {
|
|
382
|
+
unsigned scaledMargin = (unsigned)(m_scale * m_margins);
|
|
383
|
+
for (auto& page : m_pages) {
|
|
384
|
+
page.imageScale = m_scale;
|
|
385
|
+
page.scaledWidth = (unsigned)(page.width * m_scale);
|
|
386
|
+
page.scaledHeight = (unsigned)(page.height * m_scale);
|
|
387
|
+
page.image.Margin(ThicknessHelper::FromUniformLength(scaledMargin));
|
|
388
|
+
page.image.Width(page.scaledWidth);
|
|
389
|
+
page.image.Height(page.scaledHeight);
|
|
390
|
+
}
|
|
391
|
+
unsigned totalTopOffset = 0;
|
|
392
|
+
unsigned totalLeftOffset = 0;
|
|
393
|
+
unsigned doubleScaledMargin = scaledMargin * 2;
|
|
394
|
+
if (m_reverse) {
|
|
395
|
+
for (int page = m_pages.size() - 1; page >= 0; --page) {
|
|
396
|
+
m_pages[page].scaledTopOffset = totalTopOffset;
|
|
397
|
+
totalTopOffset += m_pages[page].scaledHeight + doubleScaledMargin;
|
|
398
|
+
m_pages[page].scaledLeftOffset = totalLeftOffset;
|
|
399
|
+
totalLeftOffset += m_pages[page].scaledWidth + doubleScaledMargin;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
for (int page = 0; page < (int)m_pages.size(); ++page) {
|
|
404
|
+
m_pages[page].scaledTopOffset = totalTopOffset;
|
|
405
|
+
totalTopOffset += m_pages[page].scaledHeight + doubleScaledMargin;
|
|
406
|
+
m_pages[page].scaledLeftOffset = totalLeftOffset;
|
|
407
|
+
totalLeftOffset += m_pages[page].scaledWidth + doubleScaledMargin;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
void RCTPdfControl::GoToPage(int page) {
|
|
413
|
+
if (page < 0 || page >= (int)m_pages.size()) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (m_enablePaging) {
|
|
417
|
+
[&](int page) -> winrt::fire_and_forget {
|
|
418
|
+
auto lifetime = get_strong();
|
|
419
|
+
co_await m_pages[page].render();
|
|
420
|
+
}(page);
|
|
421
|
+
Pages().Items().ReplaceAll({ &m_pages[page].image, &m_pages[page].image + 1 });
|
|
422
|
+
} else {
|
|
423
|
+
auto neededOffset = m_horizontal ? m_pages[page].scaledLeftOffset : m_pages[page].scaledTopOffset;
|
|
424
|
+
double horizontalOffset = m_horizontal ? neededOffset : PagesContainer().HorizontalOffset();
|
|
425
|
+
double verticalOffset = m_horizontal ? PagesContainer().VerticalOffset() : neededOffset;
|
|
426
|
+
ChangeScroll(horizontalOffset, verticalOffset);
|
|
427
|
+
}
|
|
428
|
+
SignalPageChange(page + 1, m_pages.size());
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
winrt::fire_and_forget RCTPdfControl::LoadPDF(std::unique_lock<std::shared_mutex> lock, int fitPolicy, bool singlePage) {
|
|
432
|
+
auto lifetime = get_strong();
|
|
433
|
+
auto pdfURI = m_pdfURI;
|
|
434
|
+
auto uri = Uri(winrt::to_hstring(pdfURI));
|
|
435
|
+
auto scheme = uri.SchemeName();
|
|
436
|
+
if (scheme == L"file") {
|
|
437
|
+
// backslashes only on Windows
|
|
438
|
+
std::replace(begin(pdfURI), end(pdfURI), '/', '\\');
|
|
439
|
+
}
|
|
440
|
+
PdfDocument document = nullptr;
|
|
441
|
+
try {
|
|
442
|
+
auto file = scheme == L"file"
|
|
443
|
+
? co_await StorageFile::GetFileFromPathAsync(winrt::to_hstring(pdfURI))
|
|
444
|
+
: co_await StorageFile::GetFileFromApplicationUriAsync(uri);
|
|
445
|
+
document = co_await PdfDocument::LoadFromFileAsync(file, winrt::to_hstring(m_pdfPassword));
|
|
446
|
+
}
|
|
447
|
+
catch (winrt::hresult_error const& ex) {
|
|
448
|
+
switch (ex.to_abi()) {
|
|
449
|
+
case __HRESULT_FROM_WIN32(ERROR_WRONG_PASSWORD):
|
|
450
|
+
SignalError("Password required or incorrect password.");
|
|
451
|
+
co_return;
|
|
452
|
+
case E_FAIL:
|
|
453
|
+
SignalError("Document is not a valid PDF.");
|
|
454
|
+
co_return;
|
|
455
|
+
default:
|
|
456
|
+
SignalError(winrt::to_string(ex.message()));
|
|
457
|
+
co_return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (!document) {
|
|
461
|
+
SignalError("Could not load PDF.");
|
|
462
|
+
co_return;
|
|
463
|
+
}
|
|
464
|
+
auto items = Pages().Items();
|
|
465
|
+
items.Clear();
|
|
466
|
+
m_pages.clear();
|
|
467
|
+
SetOrientation(m_horizontal);
|
|
468
|
+
if (document.PageCount() == 0) {
|
|
469
|
+
if (fitPolicy != -1)
|
|
470
|
+
m_scale = 1;
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
auto firstPageSize = document.GetPage(0).Size();
|
|
474
|
+
auto viewWidth = PagesContainer().ViewportWidth();
|
|
475
|
+
auto viewHeight = PagesContainer().ViewportHeight();
|
|
476
|
+
switch (fitPolicy) {
|
|
477
|
+
case 0:
|
|
478
|
+
m_scale = viewWidth / (firstPageSize.Width + 2 * (double)m_margins);
|
|
479
|
+
break;
|
|
480
|
+
case 1:
|
|
481
|
+
m_scale = viewHeight / (firstPageSize.Height + 2 * (double)m_margins);
|
|
482
|
+
break;
|
|
483
|
+
case 2:
|
|
484
|
+
m_scale = (std::min)(viewWidth / (firstPageSize.Width + 2 * (double)m_margins), viewHeight / (firstPageSize.Height + 2 * (double)m_margins));
|
|
485
|
+
break;
|
|
486
|
+
default:
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
unsigned pagesCount = document.PageCount();
|
|
491
|
+
if (singlePage && pagesCount > 0)
|
|
492
|
+
pagesCount = 1;
|
|
493
|
+
for (unsigned pageIdx = 0; pageIdx < pagesCount; ++pageIdx) {
|
|
494
|
+
auto page = document.GetPage(pageIdx);
|
|
495
|
+
auto dims = page.Size();
|
|
496
|
+
Image pageImage;
|
|
497
|
+
pageImage.HorizontalAlignment(HorizontalAlignment::Center);
|
|
498
|
+
pageImage.AllowFocusOnInteraction(false);
|
|
499
|
+
Automation::AutomationProperties::SetName(pageImage, winrt::to_hstring("PDF Page " + std::to_string(pageIdx + 1)));
|
|
500
|
+
m_pages.emplace_back(pageImage, page, m_scale, 0);
|
|
501
|
+
}
|
|
502
|
+
if (m_enablePaging) {
|
|
503
|
+
items.Append(m_pages[m_currentPage].image);
|
|
504
|
+
} else {
|
|
505
|
+
if (m_reverse) {
|
|
506
|
+
for (int page = m_pages.size() - 1; page >= 0; --page)
|
|
507
|
+
items.Append(m_pages[page].image);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
for (int page = 0; page < (int)m_pages.size(); ++page)
|
|
511
|
+
items.Append(m_pages[page].image);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
UpdatePagesInfoMarginOrScale();
|
|
515
|
+
lock.unlock();
|
|
516
|
+
std::shared_lock shared_lock(m_rwlock);
|
|
517
|
+
if (m_currentPage < 0)
|
|
518
|
+
m_currentPage = 0;
|
|
519
|
+
if (m_currentPage < (int)m_pages.size()) {
|
|
520
|
+
co_await m_pages[m_currentPage].render();
|
|
521
|
+
GoToPage(m_currentPage);
|
|
522
|
+
}
|
|
523
|
+
if (m_pages.empty()) {
|
|
524
|
+
SignalLoadComplete(0, 0, 0);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
SignalLoadComplete(m_pages.size(), m_pages.front().width, m_pages.front().height);
|
|
528
|
+
}
|
|
529
|
+
// Render low-res preview of the pages
|
|
530
|
+
double useScale = (std::min)(m_scale, m_previewZoom);
|
|
531
|
+
for (unsigned page = 0; page < m_pages.size(); ++page) {
|
|
532
|
+
co_await m_pages[page].render(useScale);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
void RCTPdfControl::Rescale(double newScale, double newMargin, bool goToNewPosition) {
|
|
537
|
+
if (newScale != m_scale || newMargin != m_margins) {
|
|
538
|
+
double rescale = newScale / m_scale;
|
|
539
|
+
double targetHorizontalOffset = PagesContainer().HorizontalOffset() * rescale;
|
|
540
|
+
double targetVerticalOffset = PagesContainer().VerticalOffset() * rescale;
|
|
541
|
+
if (newMargin != m_margins) {
|
|
542
|
+
if (m_horizontal) {
|
|
543
|
+
targetVerticalOffset += (double)m_currentPage * 2 * (newMargin - m_margins) * rescale;
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
targetHorizontalOffset += (double)m_currentPage * 2 * (newMargin - m_margins) * rescale;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
m_scale = newScale;
|
|
550
|
+
m_margins = (int)newMargin;
|
|
551
|
+
UpdatePagesInfoMarginOrScale();
|
|
552
|
+
if (goToNewPosition) {
|
|
553
|
+
ChangeScroll(targetHorizontalOffset, targetVerticalOffset);
|
|
554
|
+
}
|
|
555
|
+
SignalScaleChanged(m_scale);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
void RCTPdfControl::SetOrientation(bool horizontal) {
|
|
560
|
+
m_horizontal = horizontal;
|
|
561
|
+
StackPanel orientationSelector;
|
|
562
|
+
if (FindName(winrt::to_hstring("OrientationSelector")).try_as<StackPanel>(orientationSelector))
|
|
563
|
+
{
|
|
564
|
+
orientationSelector.Orientation(m_horizontal ? Orientation::Horizontal : Orientation::Vertical);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
winrt::Windows::Foundation::IAsyncAction RCTPdfControl::RenderVisiblePages(int page) {
|
|
569
|
+
auto lifetime = get_strong();
|
|
570
|
+
auto container = PagesContainer();
|
|
571
|
+
auto currentHorizontalOffset = container.HorizontalOffset();
|
|
572
|
+
auto currentVerticalOffset = container.VerticalOffset();
|
|
573
|
+
double offsetStart = m_horizontal ? currentHorizontalOffset : currentVerticalOffset;
|
|
574
|
+
double viewSize = m_horizontal ? container.ViewportWidth() : container.ViewportHeight();
|
|
575
|
+
double offsetEnd = offsetStart + viewSize;
|
|
576
|
+
if (m_pages[page].needsRender()) {
|
|
577
|
+
co_await m_pages[page].render();
|
|
578
|
+
}
|
|
579
|
+
auto pageToRender = page + 1;
|
|
580
|
+
while (pageToRender < (int)m_pages.size() &&
|
|
581
|
+
m_pages[pageToRender].pageVisiblePixels(m_horizontal, offsetStart, offsetEnd) > 0) {
|
|
582
|
+
if (m_pages[pageToRender].needsRender()) {
|
|
583
|
+
co_await m_pages[pageToRender].render();
|
|
584
|
+
}
|
|
585
|
+
++pageToRender;
|
|
586
|
+
}
|
|
587
|
+
if (pageToRender < (int)m_pages.size() && m_pages[pageToRender].needsRender()) {
|
|
588
|
+
co_await m_pages[pageToRender].render();
|
|
589
|
+
}
|
|
590
|
+
if (page >= 1 && m_pages[page - 1].needsRender()) {
|
|
591
|
+
co_await m_pages[page - 1].render();
|
|
592
|
+
}
|
|
593
|
+
if (page >= 2 && m_pages[page - 2].needsRender()) {
|
|
594
|
+
co_await m_pages[page - 2].render();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
void RCTPdfControl::SignalError(const std::string& error) {
|
|
599
|
+
m_reactContext.DispatchEvent(
|
|
600
|
+
*this,
|
|
601
|
+
L"topChange",
|
|
602
|
+
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
|
|
603
|
+
eventDataWriter.WriteObjectBegin();
|
|
604
|
+
{
|
|
605
|
+
WriteProperty(eventDataWriter, L"message", winrt::to_hstring("error|" + error));
|
|
606
|
+
}
|
|
607
|
+
eventDataWriter.WriteObjectEnd();
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
void RCTPdfControl::SignalLoadComplete(int totalPages, int width, int height) {
|
|
611
|
+
auto message = "loadComplete|" +
|
|
612
|
+
std::to_string(totalPages) + "|" +
|
|
613
|
+
std::to_string(width) + "|" +
|
|
614
|
+
std::to_string(height) + "|";
|
|
615
|
+
m_reactContext.DispatchEvent(
|
|
616
|
+
*this,
|
|
617
|
+
L"topChange",
|
|
618
|
+
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
|
|
619
|
+
eventDataWriter.WriteObjectBegin();
|
|
620
|
+
{
|
|
621
|
+
WriteProperty(eventDataWriter, L"message", winrt::to_hstring(message));
|
|
622
|
+
}
|
|
623
|
+
eventDataWriter.WriteObjectEnd();
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
void RCTPdfControl::SignalPageChange(int page, int totalPages) {
|
|
627
|
+
auto message = "pageChanged|" + std::to_string(page) + "|" + std::to_string(totalPages);
|
|
628
|
+
m_reactContext.DispatchEvent(
|
|
629
|
+
*this,
|
|
630
|
+
L"topChange",
|
|
631
|
+
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
|
|
632
|
+
eventDataWriter.WriteObjectBegin();
|
|
633
|
+
{
|
|
634
|
+
WriteProperty(eventDataWriter, L"message", winrt::to_hstring(message));
|
|
635
|
+
}
|
|
636
|
+
eventDataWriter.WriteObjectEnd();
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
void RCTPdfControl::SignalScaleChanged(double scale) {
|
|
640
|
+
m_reactContext.DispatchEvent(
|
|
641
|
+
*this,
|
|
642
|
+
L"topChange",
|
|
643
|
+
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
|
|
644
|
+
eventDataWriter.WriteObjectBegin();
|
|
645
|
+
{
|
|
646
|
+
WriteProperty(eventDataWriter, L"message", winrt::to_hstring("scale|" + std::to_string(scale)));
|
|
647
|
+
}
|
|
648
|
+
eventDataWriter.WriteObjectEnd();
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
void RCTPdfControl::SignalPageTapped(int page, int x, int y) {
|
|
652
|
+
const std::string message = "pageSingleTap|" +
|
|
653
|
+
std::to_string(page + 1) + "|" +
|
|
654
|
+
std::to_string(x) + "|" +
|
|
655
|
+
std::to_string(y);
|
|
656
|
+
m_reactContext.DispatchEvent(
|
|
657
|
+
*this,
|
|
658
|
+
L"topChange",
|
|
659
|
+
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
|
|
660
|
+
eventDataWriter.WriteObjectBegin();
|
|
661
|
+
{
|
|
662
|
+
WriteProperty(eventDataWriter, L"message", winrt::to_hstring(message));
|
|
663
|
+
}
|
|
664
|
+
eventDataWriter.WriteObjectEnd();
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|