@react-native-ohos/react-native-pdf 6.7.5-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 +416 -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 +46 -0
- package/fabric/RTNPdfViewNativeComponent.tsx +56 -0
- package/harmony/pdfview/Index.ets +8 -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.json5 +11 -0
- package/harmony/pdfview/src/main/cpp/CMakeLists.txt +8 -0
- package/harmony/pdfview/src/main/cpp/ComponentDescriptors.h +39 -0
- package/harmony/pdfview/src/main/cpp/EventEmitters.cpp +40 -0
- package/harmony/pdfview/src/main/cpp/EventEmitters.h +50 -0
- package/harmony/pdfview/src/main/cpp/PdfEventEmitRequestHandler.h +53 -0
- package/harmony/pdfview/src/main/cpp/PdfViewJSIBinder.h +71 -0
- package/harmony/pdfview/src/main/cpp/PdfViewPackage.h +58 -0
- package/harmony/pdfview/src/main/cpp/Props.cpp +60 -0
- package/harmony/pdfview/src/main/cpp/Props.h +65 -0
- package/harmony/pdfview/src/main/cpp/RTNPdfViewSpecsJSI-generated.cpp +30 -0
- package/harmony/pdfview/src/main/cpp/RTNPdfViewSpecsJSI.h +33 -0
- package/harmony/pdfview/src/main/cpp/ShadowNodes.cpp +33 -0
- package/harmony/pdfview/src/main/cpp/ShadowNodes.h +48 -0
- package/harmony/pdfview/src/main/cpp/States.cpp +30 -0
- package/harmony/pdfview/src/main/cpp/States.h +57 -0
- package/harmony/pdfview/src/main/ets/Logger.ets +64 -0
- package/harmony/pdfview/src/main/ets/PdfViewPackage.ets +34 -0
- package/harmony/pdfview/src/main/ets/components/mainpage/RTNPdfView.ets +521 -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 +64 -0
- package/index.js +475 -0
- package/index.js.flow +65 -0
- package/package.json +63 -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,521 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2024 Huawei Device Co., Ltd.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Descriptor, ComponentBuilderContext, ViewRawProps, ViewBaseProps } from '@rnoh/react-native-openharmony';
|
|
26
|
+
import { RNOHContext, RNViewBase } from '@rnoh/react-native-openharmony';
|
|
27
|
+
import { BusinessError } from '@kit.BasicServicesKit';
|
|
28
|
+
import fs from '@ohos.file.fs';
|
|
29
|
+
import request from '@ohos.request';
|
|
30
|
+
import buffer from '@ohos.buffer';
|
|
31
|
+
import cryptoFramework from '@ohos.security.cryptoFramework';
|
|
32
|
+
import { pdfService, pdfViewManager, PdfView } from '@kit.PDFKit'
|
|
33
|
+
import { common } from '@kit.AbilityKit';
|
|
34
|
+
import { ON_PROGRESS_CHANGE } from './types';
|
|
35
|
+
import Logger from '../../Logger';
|
|
36
|
+
|
|
37
|
+
export const PDF_VIEW_TYPE: string = "RTNPdfView";
|
|
38
|
+
|
|
39
|
+
export interface PdfViewState {}
|
|
40
|
+
|
|
41
|
+
interface Source {
|
|
42
|
+
uri: string;
|
|
43
|
+
headers?: Record<string, Object>;
|
|
44
|
+
cache?: boolean;
|
|
45
|
+
cacheFileName?: string;
|
|
46
|
+
expiration?: number;
|
|
47
|
+
method?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export interface PdfViewRawProps extends ViewRawProps {
|
|
51
|
+
source: Source,
|
|
52
|
+
path?: string,
|
|
53
|
+
page?: number,
|
|
54
|
+
scale?: number,
|
|
55
|
+
minScale?: number,
|
|
56
|
+
maxScale?: number,
|
|
57
|
+
horizontal?: boolean,
|
|
58
|
+
enablePaging?: boolean,
|
|
59
|
+
enableRTL?: boolean,
|
|
60
|
+
enableAnnotationRendering?: boolean,
|
|
61
|
+
showsHorizontalScrollIndicator?: boolean,
|
|
62
|
+
showsVerticalScrollIndicator?: boolean,
|
|
63
|
+
enableAntialiasing?: boolean,
|
|
64
|
+
fitPolicy?: number,
|
|
65
|
+
spacing?: number,
|
|
66
|
+
password?: string,
|
|
67
|
+
singlePage?: boolean,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type PdfViewDescriptor = Descriptor<'RTNPdfView', ViewBaseProps, PdfViewState, PdfViewRawProps>
|
|
71
|
+
|
|
72
|
+
@Component
|
|
73
|
+
export struct RTNPdfView {
|
|
74
|
+
ctx!: RNOHContext;
|
|
75
|
+
tag: number = 0;
|
|
76
|
+
@BuilderParam buildCustomComponent: (componentBuilderContext: ComponentBuilderContext) => void = this.emptyBuild;
|
|
77
|
+
@State descriptor: PdfViewDescriptor = Object() as PdfViewDescriptor;
|
|
78
|
+
@State pageIndex: number = 1;
|
|
79
|
+
@State controllerStatus: boolean = false;
|
|
80
|
+
@State pdfUrl: string = "";
|
|
81
|
+
private unregisterDescriptorChangesListener?: () => void = undefined;
|
|
82
|
+
private cleanupCommandCallback?: () => void = undefined;
|
|
83
|
+
private pdfController: pdfViewManager.PdfController = new pdfViewManager.PdfController();
|
|
84
|
+
private totalPages: number = 0;
|
|
85
|
+
|
|
86
|
+
@Builder
|
|
87
|
+
emptyBuild(ctx: ComponentBuilderContext) {
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async doLoadHttpDocument(path: string): Promise<void> {
|
|
91
|
+
Logger.debug(`[RTNPdfView]: doLoadHttpDocument, path is ${path}`);
|
|
92
|
+
try {
|
|
93
|
+
const filePath: string = await this.downloadHttpFile(path);
|
|
94
|
+
Logger.debug(`[RTNPdfView]: doLoadHttpDocument, filePath is ${filePath}`);
|
|
95
|
+
this.doLoadLocalDocument(filePath, !this.descriptor.rawProps.source?.cache);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
Logger.error(`[RTNPdfView]: fs accessSync: ${e}`);
|
|
98
|
+
this.onError(`[RTNPdfView]: fs accessSync: ${e}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
doLoadLocalDocument(filePath: string, isDelOriginFile: boolean = true): void {
|
|
103
|
+
let initPageIndex: number | undefined = this.descriptor.rawProps.page;
|
|
104
|
+
let spacing: number = Number(this.descriptor.rawProps.spacing);
|
|
105
|
+
let scale: number = Number(this.descriptor.rawProps.scale);
|
|
106
|
+
let minScale: number | undefined = this.descriptor.rawProps.minScale;
|
|
107
|
+
let maxScale: number | undefined = this.descriptor.rawProps.maxScale;
|
|
108
|
+
let password: string | undefined = this.descriptor.rawProps.password;
|
|
109
|
+
let fitPolicy: number | undefined = this.descriptor.rawProps.fitPolicy;
|
|
110
|
+
// pdfView 初始 scale
|
|
111
|
+
let systemInitScale = 0;
|
|
112
|
+
|
|
113
|
+
Logger.debug(`[RTNPdfView]: doLoadLocalDocument, filePath is ${filePath}, isDelOriginFile is ${isDelOriginFile}`);
|
|
114
|
+
const progressCallBack: (progress: number) => number = (progress: number) => {
|
|
115
|
+
Logger.debug(`[RTNPdfView]: proress is ${(progress / 100).toFixed(2)}`);
|
|
116
|
+
this.loadProgress(Number((progress / 100).toFixed(2)));
|
|
117
|
+
return 0;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (filePath) {
|
|
121
|
+
this.pdfController.releaseDocument();
|
|
122
|
+
const initPage = initPageIndex ? Math.max(initPageIndex - 1, 0) : 0
|
|
123
|
+
this.pdfController.loadDocument(filePath, password, initPage, progressCallBack)
|
|
124
|
+
.then((res: pdfService.ParseResult) => {
|
|
125
|
+
switch (res) {
|
|
126
|
+
case pdfService.ParseResult.PARSE_SUCCESS:
|
|
127
|
+
this.pdfController.setPageSpacing(spacing, spacing); //由于rn侧值传递一个参数,故此处默认为连续滚动模式。设置上下面的间距。
|
|
128
|
+
if(scale!==1){
|
|
129
|
+
this.pdfController.setPageZoom(scale);
|
|
130
|
+
}
|
|
131
|
+
const pageCount: number = this.pdfController.getPageCount();
|
|
132
|
+
if (typeof fitPolicy === 'number') {
|
|
133
|
+
if (fitPolicy === 0) {
|
|
134
|
+
this.pdfController.setPageFit(pdfService.PageFit.FIT_WIDTH);
|
|
135
|
+
} else if (fitPolicy === 1) {
|
|
136
|
+
this.pdfController.setPageFit(pdfService.PageFit.FIT_HEIGHT);
|
|
137
|
+
} else if (fitPolicy === 2) {
|
|
138
|
+
this.pdfController.setPageFit(pdfService.PageFit.FIT_PAGE);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 注册监听器
|
|
143
|
+
let tempScale: number | undefined = scale;
|
|
144
|
+
this.pdfController.registerScaleChangedListener((currentScale: number) => {
|
|
145
|
+
if (tempScale !== currentScale) {
|
|
146
|
+
if (typeof maxScale === 'number' && currentScale > maxScale) {
|
|
147
|
+
this.pdfController.setPageZoom(maxScale);
|
|
148
|
+
} else if (typeof minScale === 'number' && currentScale < minScale) {
|
|
149
|
+
// 问题:当minScale为1时,其他平台不缩放,但鸿蒙pdfView的初始scale小于1,在这里会强制赋值为1,这样就会放大产生滚动,引起和其他平台不一致的效果
|
|
150
|
+
// 解决:systemInitScale为0时,currentScale表示pdfView初始scale,若小于minScale,则赋值给minScale,这样就不会强制赋值为1而产生缩放
|
|
151
|
+
if (!systemInitScale && minScale === 1) {
|
|
152
|
+
systemInitScale = currentScale
|
|
153
|
+
minScale = systemInitScale;
|
|
154
|
+
} else {
|
|
155
|
+
this.pdfController.setPageZoom(minScale);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
tempScale = currentScale;
|
|
159
|
+
}
|
|
160
|
+
this.onScaleChanged(currentScale);
|
|
161
|
+
});
|
|
162
|
+
this.pdfController.registerPageChangedListener((pageIndex: number) => {
|
|
163
|
+
this.onPageChanged((pageIndex + 1), pageCount);
|
|
164
|
+
});
|
|
165
|
+
this.pdfController.registerActionClickListener((redirectInfo: pdfViewManager.RedirectInfo) => {
|
|
166
|
+
this.onPressLink(redirectInfo.content);
|
|
167
|
+
});
|
|
168
|
+
this.loadComplete(pageCount, filePath,);
|
|
169
|
+
Logger.debug(`[RTNPdfView]: loadDocument success`);
|
|
170
|
+
break;
|
|
171
|
+
case pdfService.ParseResult.PARSE_ERROR_FILE:
|
|
172
|
+
this.onError('loadDocument error: PARSE_ERROR_FILE');
|
|
173
|
+
break;
|
|
174
|
+
case pdfService.ParseResult.PARSE_ERROR_FORMAT:
|
|
175
|
+
this.onError('loadDocument error: PARSE_ERROR_FORMAT');
|
|
176
|
+
break;
|
|
177
|
+
case pdfService.ParseResult.PARSE_ERROR_PASSWORD:
|
|
178
|
+
this.onError('loadDocument error: PARSE_ERROR_PASSWORD');
|
|
179
|
+
break;
|
|
180
|
+
case pdfService.ParseResult.PARSE_ERROR_HANDLER:
|
|
181
|
+
this.onError('loadDocument error: PARSE_ERROR_HANDLER');
|
|
182
|
+
break;
|
|
183
|
+
case pdfService.ParseResult.PARSE_ERROR_CERT:
|
|
184
|
+
this.onError('loadDocument error: PARSE_ERROR_CERT');
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
this.onError('loadDocument error: UNKNOWN');
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.catch((e: Error) => {
|
|
192
|
+
Logger.error(`[RTNPdfView]: loadDocument error: ${e}`);
|
|
193
|
+
this.onError(e.message);
|
|
194
|
+
})
|
|
195
|
+
.finally(() => {
|
|
196
|
+
Logger.debug(`[RTNPdfView]: isDelOriginFile is : ${isDelOriginFile}`);
|
|
197
|
+
if (isDelOriginFile) {
|
|
198
|
+
fs.unlink(filePath).then(() => {
|
|
199
|
+
Logger.debug(`[RTNPdfView]: originFile deleted`);
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async downloadHttpFile(url: string): Promise<string> {
|
|
207
|
+
let context = getContext(this) as common.UIAbilityContext;
|
|
208
|
+
let filesDir = context.filesDir;
|
|
209
|
+
let res: string = "";
|
|
210
|
+
const hashFileName: string = await this.processFileName(url);
|
|
211
|
+
const cacheFileName = await this.checkCache(hashFileName);
|
|
212
|
+
if(this.descriptor.rawProps.source?.cache && !!cacheFileName){
|
|
213
|
+
res = cacheFileName;
|
|
214
|
+
}else{
|
|
215
|
+
if (!!cacheFileName && fs.accessSync(cacheFileName)) {
|
|
216
|
+
fs.unlinkSync(cacheFileName);
|
|
217
|
+
}
|
|
218
|
+
const fullFileName: string = filesDir + "/" + hashFileName + new Date().getTime() + '.pdf';
|
|
219
|
+
res = await this.downloadAction(context, url, fullFileName, {}, 0);
|
|
220
|
+
}
|
|
221
|
+
Logger.info(`[RTNPdfView]:res: ${res}`);
|
|
222
|
+
return res;
|
|
223
|
+
}
|
|
224
|
+
async downloadAction(
|
|
225
|
+
context: Context,
|
|
226
|
+
url: string,
|
|
227
|
+
fullFileName: string,
|
|
228
|
+
distHeaders: Record<string, Object>,
|
|
229
|
+
redirectCount: number = 0,
|
|
230
|
+
): Promise<string> {
|
|
231
|
+
if (redirectCount > 5) {
|
|
232
|
+
throw new Error("Too many redirects");
|
|
233
|
+
}
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
// 获取应用文件路径
|
|
236
|
+
try {
|
|
237
|
+
Logger.info(`[RTNPdfView]: start downloadFile`);
|
|
238
|
+
const config: request.agent.Config = {
|
|
239
|
+
action: request.agent.Action.DOWNLOAD,
|
|
240
|
+
url,
|
|
241
|
+
method: this.descriptor.rawProps.source.method ?? 'GET',
|
|
242
|
+
headers: assignHeader(this.descriptor.rawProps.source?.headers || {}, distHeaders|| {}),
|
|
243
|
+
saveas: fullFileName,
|
|
244
|
+
overwrite: true,
|
|
245
|
+
mode: request.agent.Mode.FOREGROUND
|
|
246
|
+
};
|
|
247
|
+
request.agent.create(context, config).then((task: request.agent.Task) => {
|
|
248
|
+
task.on('completed', () => {
|
|
249
|
+
Logger.info(`[RTNPdfView]: finish downloadFile`);
|
|
250
|
+
resolve(fullFileName);
|
|
251
|
+
})
|
|
252
|
+
task.on('failed', (progress: request.agent.Progress) => {
|
|
253
|
+
console.info('[RTNPdfView]: downloadFile failed.', JSON.stringify(progress));
|
|
254
|
+
});
|
|
255
|
+
task.on('response', async (response: request.agent.HttpResponse) => {
|
|
256
|
+
console.info('[RTNPdfView]: downloadFile response.', JSON.stringify(response));
|
|
257
|
+
if (response.statusCode > 300 && response.statusCode < 400) {
|
|
258
|
+
let redirectUrl: string = response.headers.get('location')?.toString() ?? '';
|
|
259
|
+
let cookie: string = response.headers.get('set-cookie')?.toString() ?? '';
|
|
260
|
+
task.off('progress');
|
|
261
|
+
task.off('completed');
|
|
262
|
+
task.off('response');
|
|
263
|
+
task.stop();
|
|
264
|
+
try {
|
|
265
|
+
const result = await this.downloadAction(context, redirectUrl, fullFileName, { 'cookie': cookie }, redirectCount + 1);
|
|
266
|
+
|
|
267
|
+
Logger.info(`[RTNPdfView]:result: ${result} -----count: ${redirectCount}}`);
|
|
268
|
+
resolve(result)
|
|
269
|
+
} catch (e) {
|
|
270
|
+
reject(e);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
task.start();
|
|
275
|
+
}).catch((err: BusinessError) => {
|
|
276
|
+
Logger.info(`[RTNPdfView]: error downloadHttpFile`);
|
|
277
|
+
this.onError(err.message);
|
|
278
|
+
reject("downloadFile failed" + err.message);
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
Logger.info(`[RTNPdfView]: error downloadHttpFile`);
|
|
282
|
+
let err: BusinessError = error as BusinessError;
|
|
283
|
+
this.onError(err.message);
|
|
284
|
+
reject("downloadFile failed" + err.message);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async processFileName(originalURL: string): Promise<string> {
|
|
290
|
+
return new Promise(async (resolve, reject) => {
|
|
291
|
+
let mdAlgName = 'SHA256'; // 摘要算法名
|
|
292
|
+
let md = cryptoFramework.createMd(mdAlgName);
|
|
293
|
+
try {
|
|
294
|
+
await md.update({ data: new Uint8Array(buffer.from(originalURL, 'utf-8').buffer) });
|
|
295
|
+
let mdResult: cryptoFramework.DataBlob = await md.digest();
|
|
296
|
+
const fileName: string = buffer.from(mdResult.data).toString('hex');
|
|
297
|
+
Logger.info(`[RTNPdfView]: fileName: ${fileName}`);
|
|
298
|
+
resolve(fileName);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
this.onError(e.message);
|
|
301
|
+
reject("processFileName error");
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
async checkCache(hashFileName: string): Promise<string> {
|
|
306
|
+
try {
|
|
307
|
+
let filesDir = (getContext() as common.UIAbilityContext).filesDir;
|
|
308
|
+
let cacheFileName: string = "";
|
|
309
|
+
let fileList = fs.listFileSync(filesDir);
|
|
310
|
+
fileList.forEach(file=>{
|
|
311
|
+
let _hashFileName = file.slice(0, -17);
|
|
312
|
+
_hashFileName === hashFileName && (cacheFileName = filesDir + "/" + file);
|
|
313
|
+
})
|
|
314
|
+
return Promise.resolve(cacheFileName);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
this.onError(e.message);
|
|
317
|
+
return Promise.reject("checkCache error");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async sendToSandBox(assetPath: string): Promise<string> {
|
|
322
|
+
assetPath = assetPath!.replace("asset://", "assets/");
|
|
323
|
+
Logger.info(`[RTNPdfView]: load pdf assetPath: ${assetPath}`);
|
|
324
|
+
const hashFileName: string = await this.processFileName(assetPath);
|
|
325
|
+
const cacheFileName = await this.checkCache(hashFileName);
|
|
326
|
+
let fileFullPath: string = "";
|
|
327
|
+
|
|
328
|
+
if(this.descriptor.rawProps.source?.cache && !!cacheFileName){
|
|
329
|
+
fileFullPath = cacheFileName;
|
|
330
|
+
}else{
|
|
331
|
+
if (!!cacheFileName && fs.accessSync(cacheFileName)) {
|
|
332
|
+
fs.unlinkSync(cacheFileName);
|
|
333
|
+
}
|
|
334
|
+
// 需将pdf文档复制到沙箱路径下
|
|
335
|
+
let context = getContext() as common.UIAbilityContext;
|
|
336
|
+
let content = context.resourceManager.getRawFileContentSync(`${assetPath}`); // pdf_reference
|
|
337
|
+
let dir = context.filesDir;
|
|
338
|
+
fileFullPath = dir + '/' + hashFileName + new Date().getTime() + '.pdf';
|
|
339
|
+
Logger.info(`[RTNPdfView]: load pdf filePath: ${fileFullPath}`);
|
|
340
|
+
let res = fs.accessSync(fileFullPath);
|
|
341
|
+
Logger.info(`[RTNPdfView]: fs accessSync: ${res}`);
|
|
342
|
+
let fdSand = fs.openSync(fileFullPath, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
|
|
343
|
+
fs.writeSync(fdSand.fd, content.buffer);
|
|
344
|
+
fs.closeSync(fdSand.fd);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return fileFullPath;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async updateSource() {
|
|
351
|
+
let src: string | undefined = this.descriptor.rawProps.path;
|
|
352
|
+
Logger.info(`[RTNPdfView]: src is ${src}`);
|
|
353
|
+
if (src) {
|
|
354
|
+
this.pdfUrl = src;
|
|
355
|
+
if (src!.startsWith('http://') || src!.startsWith('https://')) {
|
|
356
|
+
this.doLoadHttpDocument(src); // 适配加载在线文件
|
|
357
|
+
} else {
|
|
358
|
+
let isDeleteOriginFile: boolean = false;
|
|
359
|
+
if (src!.startsWith('asset://')) { // 适配require('../assets/test.pdf')
|
|
360
|
+
src = await this.sendToSandBox(src);
|
|
361
|
+
isDeleteOriginFile = true;
|
|
362
|
+
}
|
|
363
|
+
this.doLoadLocalDocument(src, isDeleteOriginFile); // 适配本地其他目录,比如cache
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
aboutToAppear() {
|
|
369
|
+
this.descriptor = this.ctx.descriptorRegistry.getDescriptor<PdfViewDescriptor>(this.tag);
|
|
370
|
+
this.updateSource();
|
|
371
|
+
this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag,
|
|
372
|
+
(newDescriptor) => {
|
|
373
|
+
this.descriptor = (newDescriptor as PdfViewDescriptor)
|
|
374
|
+
this.updateSource();
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
this.cleanupCommandCallback = this.ctx.componentCommandReceiver.registerCommandCallback(
|
|
378
|
+
this.tag,
|
|
379
|
+
(command, args: (boolean | number)[]) => {
|
|
380
|
+
Logger.info(`[RNOH]: Received command: ${command}, arg: ${args[0]}`);
|
|
381
|
+
if (command === 'setNativePage' && args.length > 0) {
|
|
382
|
+
const pageNumber = args[0] as number;
|
|
383
|
+
this.setPage(pageNumber);
|
|
384
|
+
} else {
|
|
385
|
+
Logger.info(`[RNOH]: HarmonyOS does not support,command: ${command}, arg: ${args[0]}`);
|
|
386
|
+
this.pageIndex = args[0] as number;
|
|
387
|
+
this.pdfUrl = "";
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
aboutToDisappear() {
|
|
393
|
+
this.cleanupCommandCallback?.();
|
|
394
|
+
this.unregisterDescriptorChangesListener?.();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
setPage(pageNumber: number) {
|
|
398
|
+
if (typeof pageNumber !== 'number' || isNaN(pageNumber)) {
|
|
399
|
+
Logger.error(`[RTNPdfView]: Specified pageNumber is not a number`);
|
|
400
|
+
}
|
|
401
|
+
if (pageNumber < 1 || pageNumber > this.totalPages) {
|
|
402
|
+
Logger.info(`[RTNPdfView]: setPage ignored: ${pageNumber} out of range 1..${this.totalPages}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const pageIndex = pageNumber - 1;
|
|
406
|
+
Logger.info(`[RTNPdfView]: setPage to ${pageIndex}, total pages: ${this.totalPages}`);
|
|
407
|
+
if (this.pdfController && this.totalPages > 0) {
|
|
408
|
+
this.pdfController.goToPage(pageIndex);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
loadComplete(numberOfPages: number, filePath: string) {
|
|
413
|
+
Logger.info("[RNOH]:enter loadComplete");
|
|
414
|
+
this.totalPages = numberOfPages;
|
|
415
|
+
// 获取页面宽度、高度
|
|
416
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
417
|
+
this.descriptor.tag,
|
|
418
|
+
PDF_VIEW_TYPE,
|
|
419
|
+
{
|
|
420
|
+
type: 'onChange',
|
|
421
|
+
message: `loadComplete|${numberOfPages}|100|100`
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
loadProgress(percent: number) {
|
|
427
|
+
Logger.info("[RNOH]:enter loadProgress");
|
|
428
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
429
|
+
this.descriptor.tag,
|
|
430
|
+
PDF_VIEW_TYPE,
|
|
431
|
+
{
|
|
432
|
+
type: 'onChange',
|
|
433
|
+
message: `loadProgress|${percent}`
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
onError(e: string) {
|
|
439
|
+
Logger.info("[RNOH]:enter onError");
|
|
440
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
441
|
+
this.descriptor.tag,
|
|
442
|
+
PDF_VIEW_TYPE,
|
|
443
|
+
{
|
|
444
|
+
type: 'onChange',
|
|
445
|
+
message: `error|${e}`
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
onPageChanged(page: number, numberOfPages: number) {
|
|
451
|
+
Logger.info("[RNOH]:enter pageChanged");
|
|
452
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
453
|
+
this.descriptor.tag,
|
|
454
|
+
PDF_VIEW_TYPE,
|
|
455
|
+
{
|
|
456
|
+
type: 'onChange',
|
|
457
|
+
message: `pageChanged|${page}|${numberOfPages}`
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
onPressLink(url: string) {
|
|
463
|
+
Logger.info("[RNOH]:enter onPressLink");
|
|
464
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
465
|
+
this.descriptor.tag,
|
|
466
|
+
PDF_VIEW_TYPE,
|
|
467
|
+
{
|
|
468
|
+
type: 'onChange',
|
|
469
|
+
message: `linkPressed|${url}`
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
onScaleChanged(currentScale: number) {
|
|
475
|
+
Logger.info("[RNOH]:enter scaleChanged");
|
|
476
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
477
|
+
this.descriptor.tag,
|
|
478
|
+
PDF_VIEW_TYPE,
|
|
479
|
+
{
|
|
480
|
+
type: 'onChange',
|
|
481
|
+
message: `scaleChanged|${currentScale}`
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
onChange() {
|
|
487
|
+
Logger.info("[RNOH]:onChange not support");
|
|
488
|
+
this.ctx.rnInstance.emitComponentEvent(
|
|
489
|
+
this.descriptor.tag,
|
|
490
|
+
PDF_VIEW_TYPE,
|
|
491
|
+
{
|
|
492
|
+
type: 'onChange',
|
|
493
|
+
message: ON_PROGRESS_CHANGE
|
|
494
|
+
}
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
build() {
|
|
499
|
+
RNViewBase({ ctx: this.ctx, tag: this.tag }) {
|
|
500
|
+
if (this.descriptor.rawProps.path) {
|
|
501
|
+
PdfView(
|
|
502
|
+
{
|
|
503
|
+
controller: this.pdfController,
|
|
504
|
+
pageLayout: pdfService.PageLayout.LAYOUT_SINGLE,
|
|
505
|
+
isContinuous: true,
|
|
506
|
+
pageFit: pdfService.PageFit.FIT_WIDTH,
|
|
507
|
+
showScroll: false
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function assignHeader(target: Record<string, Object>, ...source: Record<string, Object>[]): Record<string, Object> {
|
|
515
|
+
for(let s of source){
|
|
516
|
+
for(let k of Object.keys(s)){
|
|
517
|
+
target[k] = Reflect.get(s, k)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return target
|
|
521
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
|
|
3
|
+
* Use of this source code is governed by a MIT license that can be
|
|
4
|
+
* found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const PDF_VIEW:string ='RTNPdfView'
|
|
8
|
+
export const ASSET:string='asset'
|
|
9
|
+
export const LOAD_COMPLETE:string='loadComplete'
|
|
10
|
+
export const PAGE_CHANGE:string='pageChanged'
|
|
11
|
+
export const SCALE_CHANGED:string='scaleChanged'
|
|
12
|
+
export const ON_PROGRESS_CHANGE:string='onProgressChange'
|
|
13
|
+
export const HIDE_TOOLBAR:string='#toolbar=0&navpanes=0'
|
|
14
|
+
export const HTTP:string='http'
|
|
15
|
+
export const HTTPS:string='https'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2024 Huawei Device Co., Ltd.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import localUnitTest from './LocalUnit.test';
|
|
26
|
+
|
|
27
|
+
export default function testsuite() {
|
|
28
|
+
localUnitTest();
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2024 Huawei Device Co., Ltd.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
|
|
26
|
+
|
|
27
|
+
export default function localUnitTest() {
|
|
28
|
+
describe('localUnitTest',() => {
|
|
29
|
+
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
|
|
30
|
+
beforeAll(() => {
|
|
31
|
+
// Presets an action, which is performed only once before all test cases of the test suite start.
|
|
32
|
+
// This API supports only one parameter: preset action function.
|
|
33
|
+
});
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
// Presets an action, which is performed before each unit test case starts.
|
|
36
|
+
// The number of execution times is the same as the number of test cases defined by **it**.
|
|
37
|
+
// This API supports only one parameter: preset action function.
|
|
38
|
+
});
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
// Presets a clear action, which is performed after each unit test case ends.
|
|
41
|
+
// The number of execution times is the same as the number of test cases defined by **it**.
|
|
42
|
+
// This API supports only one parameter: clear action function.
|
|
43
|
+
});
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
// Presets a clear action, which is performed after all test cases of the test suite end.
|
|
46
|
+
// This API supports only one parameter: clear action function.
|
|
47
|
+
});
|
|
48
|
+
it('assertContain', 0, () => {
|
|
49
|
+
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
|
|
50
|
+
let a = 'abc';
|
|
51
|
+
let b = 'b';
|
|
52
|
+
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
|
|
53
|
+
expect(a).assertContain(b);
|
|
54
|
+
expect(a).assertEqual(a);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
Binary file
|