diff --git a/harmony/pdfview.har b/harmony/pdfview.har index 1b3b2784..108eea6f 100644 Binary files a/harmony/pdfview.har and b/harmony/pdfview.har differ diff --git a/harmony/pdfview/BuildProfile.ets b/harmony/pdfview/BuildProfile.ets index 2dbe8b51..68b7c878 100644 --- a/harmony/pdfview/BuildProfile.ets +++ b/harmony/pdfview/BuildProfile.ets @@ -1,5 +1,5 @@ -export default class BuildProfile { - static readonly HAR_VERSION = '6.7.4-0.2.2'; +export default class BuildProfile { + static readonly HAR_VERSION = '6.7.4-0.2.7-beta'; static readonly BUILD_MODE_NAME = 'debug'; static readonly DEBUG = true; static readonly TARGET_NAME = 'default'; diff --git a/harmony/pdfview/lib/PDFView.har b/harmony/pdfview/lib/PDFView.har new file mode 100644 index 00000000..c9a38b96 Binary files /dev/null and b/harmony/pdfview/lib/PDFView.har differ diff --git a/harmony/pdfview/oh-package.json5 b/harmony/pdfview/oh-package.json5 index 299a8e4d..03ab5526 100644 --- a/harmony/pdfview/oh-package.json5 +++ b/harmony/pdfview/oh-package.json5 @@ -1,11 +1,12 @@ { "name": "@react-native-oh-tpl/react-native-pdf", - "version": "6.7.4-0.2.2", + "version": "6.7.4-0.2.7-beta", "description": "Please describe the basic information.", "main": "Index.ets", "author": "", "license": "Apache-2.0", "dependencies": { - "@rnoh/react-native-openharmony": 'file:../react_native_openharmony' + "@rnoh/react-native-openharmony": 'file:../react_native_openharmony', + "PDFView": "file:./lib/PDFView.har" } } diff --git a/harmony/pdfview/src/main/cpp/PdfViewJSIBinder.h b/harmony/pdfview/src/main/cpp/PdfViewJSIBinder.h index 4d66f49a..32166d7b 100644 --- a/harmony/pdfview/src/main/cpp/PdfViewJSIBinder.h +++ b/harmony/pdfview/src/main/cpp/PdfViewJSIBinder.h @@ -50,6 +50,18 @@ namespace rnoh { object.setProperty(rt, "singlePage", "boolean"); return object; } + + facebook::jsi::Object createBubblingEventTypes(facebook::jsi::Runtime &rt) override + { + return facebook::jsi::Object(rt); + } + + facebook::jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override + { + facebook::jsi::Object events(rt); + events.setProperty(rt, "topChange", createDirectEvent(rt, "onChange")); + return events; + } }; } // namespace rnoh diff --git a/harmony/pdfview/src/main/ets/components/mainpage/RTNPdfView.ets b/harmony/pdfview/src/main/ets/components/mainpage/RTNPdfView.ets index 6b7cce48..17cd45b7 100644 --- a/harmony/pdfview/src/main/ets/components/mainpage/RTNPdfView.ets +++ b/harmony/pdfview/src/main/ets/components/mainpage/RTNPdfView.ets @@ -24,9 +24,17 @@ import { Descriptor, ComponentBuilderContext, ViewRawProps, ViewBaseProps } from '@rnoh/react-native-openharmony'; import { RNOHContext, RNViewBase } from '@rnoh/react-native-openharmony'; -import web_webview from '@ohos.web.webview'; import Logger from '../../Logger'; -import {ASSET, HIDE_TOOLBAR, HTTP, HTTPS, LOAD_COMPLETE, ON_PROGRESS_CHANGE, PAGE_CHANGE, SCALE_CHANGED,}from './types' +import { PDFController, PDFView } from 'PDFView'; +import { pdfService, pdfViewManager } from '@kit.PDFKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import common from '@ohos.app.ability.common'; +import fs from '@ohos.file.fs'; +import request from '@ohos.request'; +import buffer from '@ohos.buffer'; +import cryptoFramework from '@ohos.security.cryptoFramework'; +import { ON_PROGRESS_CHANGE } from './types'; + export const PDF_VIEW_TYPE: string = "RTNPdfView"; export interface PdfViewState {} @@ -52,11 +60,13 @@ export interface PdfViewRawProps extends ViewRawProps { export type PdfViewDescriptor = Descriptor<'RTNPdfView', ViewBaseProps, PdfViewState, PdfViewRawProps> +let context = getContext(this) as common.UIAbilityContext; +let filesDir = context.filesDir; + @Component export struct RTNPdfView { - webviewController: web_webview.WebviewController = new web_webview.WebviewController(); - ctx!: RNOHContext - tag: number = 0 + ctx!: RNOHContext; + tag: number = 0; @BuilderParam buildCustomComponent: (componentBuilderContext: ComponentBuilderContext) => void = this.emptyBuild; @State descriptor: PdfViewDescriptor = Object() as PdfViewDescriptor; @State pageIndex: number = 1; @@ -64,20 +74,202 @@ export struct RTNPdfView { @State controllerStatus: boolean = false; private unregisterDescriptorChangesListener?: () => void = undefined; private cleanupCommandCallback?: () => void = undefined; + private pdfController: PDFController = new PDFController(); @Builder emptyBuild(ctx: ComponentBuilderContext) { } - updateSource() { - let src = this.descriptor.rawProps.path; - if (src!.startsWith(`${ASSET}://`)) { - this.source = $rawfile(src!.replace(`${ASSET}://`, `${ASSET}/`)); + async doLoadHttpDocument(path: string): Promise { + Logger.debug(`[RTNPdfView]: doLoadHttpDocument, path is ${path}`); + try { + const filePath: string = await this.downloadHttpFile(path); + Logger.debug(`[RTNPdfView]: doLoadHttpDocument, filePath is ${filePath}`); + this.doLoadLocalDocument(filePath); + } catch (e) { + Logger.error(`[RTNPdfView]: fs accessSync: ${e}`); + this.onError(`[RTNPdfView]: fs accessSync: ${e}`); + } + } + + doLoadLocalDocument(filePath: string, isDelOriginFile: boolean = true): void { + let initPageIndex: number | undefined = this.descriptor.rawProps.page; + let spacing: number | undefined = this.descriptor.rawProps.spacing; + let scale: number | undefined = this.descriptor.rawProps.scale; + let minScale: number | undefined = this.descriptor.rawProps.minScale; + let maxScale: number | undefined = this.descriptor.rawProps.maxScale; + let password: string | undefined = this.descriptor.rawProps.password; + let fitPolicy: number | undefined = this.descriptor.rawProps.fitPolicy; + + Logger.debug(`[RTNPdfView]: doLoadLocalDocument, filePath is ${filePath}, isDelOriginFile is ${isDelOriginFile}`); + const progressCallBack: (progress: number) => number = (progress: number) => { + Logger.debug(`[RTNPdfView]: proress is ${(progress / 100).toFixed(2)}`); + this.loadProgress(Number((progress / 100).toFixed(2))); + return 0; + }; + + if (filePath) { + this.pdfController.loadDocument(filePath, password, initPageIndex, progressCallBack) + .then((res: pdfService.ParseResult) => { + switch (res) { + case pdfService.ParseResult.PARSE_SUCCESS: + this.pdfController.setPageSpacing(spacing, spacing); //由于rn侧值传递一个参数,故此处默认为连续滚动模式。设置上下面的间距。 + this.pdfController.setPageZoom(scale); + const pageCount: number = this.pdfController.getPageCount(); + if (typeof fitPolicy === 'number') { + if (fitPolicy === 0) { + this.pdfController.setPageFit(pdfService.PageFit.FIT_WIDTH); + } else if (fitPolicy === 1) { + this.pdfController.setPageFit(pdfService.PageFit.FIT_HEIGHT); + } else if (fitPolicy === 2) { + this.pdfController.setPageFit(pdfService.PageFit.FIT_PAGE); + } + } + + // 注册监听器 + let tempScale: number | undefined = scale; + this.pdfController.registerScaleChangedListener((currentScale: number) => { + if (tempScale !== currentScale) { + if (typeof maxScale === 'number' && currentScale > maxScale) { + this.pdfController.setPageZoom(maxScale); + } else if (typeof minScale === 'number' && currentScale < minScale) { + this.pdfController.setPageZoom(minScale); + } + tempScale = currentScale; + } + this.onScaleChanged(currentScale); + }); + this.pdfController.registerPageChangedListener((pageIndex: number) => { + this.onPageChanged((pageIndex + 1), pageCount); + }); + this.pdfController.registerActionClickListener((redirectInfo: pdfViewManager.RedirectInfo) => { + this.onPressLink(redirectInfo.content); + }); + this.loadComplete(pageCount, filePath,); + Logger.debug(`[RTNPdfView]: loadDocument success`); + break; + case pdfService.ParseResult.PARSE_ERROR_FILE: + this.onError('loadDocument error: PARSE_ERROR_FILE'); + break; + case pdfService.ParseResult.PARSE_ERROR_FORMAT: + this.onError('loadDocument error: PARSE_ERROR_FORMAT'); + break; + case pdfService.ParseResult.PARSE_ERROR_PASSWORD: + this.onError('loadDocument error: PARSE_ERROR_PASSWORD'); + break; + case pdfService.ParseResult.PARSE_ERROR_HANDLER: + this.onError('loadDocument error: PARSE_ERROR_HANDLER'); + break; + case pdfService.ParseResult.PARSE_ERROR_CERT: + this.onError('loadDocument error: PARSE_ERROR_CERT'); + break; + default: + this.onError('loadDocument error: UNKNOWN'); + break; + } + }) + .catch((e: Error) => { + Logger.error(`[RTNPdfView]: loadDocument error: ${e}`); + this.onError(e.message); + }) + .finally(() => { + Logger.debug(`[RTNPdfView]: isDelOriginFile is : ${isDelOriginFile}`); + if (isDelOriginFile) { + fs.unlink(filePath).then(() => { + Logger.debug(`[RTNPdfView]: originFile deleted`); + }) + } + }) + } + } + + async downloadHttpFile(url: string): Promise { + const hashFileName: string = await this.processFileName(url); + const fullFileName: string = filesDir + "/" + hashFileName + '.pdf'; + if (fs.accessSync(fullFileName)) { + fs.unlinkSync(fullFileName); + } + return new Promise((resolve, reject) => { + // 获取应用文件路径 + try { + Logger.info(`[RTNPdfView]: start downloadFile`); + request.downloadFile(context, { + url: url, + filePath: fullFileName + }).then((downloadTask: request.DownloadTask) => { + downloadTask.on('complete', () => { + Logger.info(`[RTNPdfView]: finish downloadFile`); + resolve(fullFileName); + }) + }).catch((err: BusinessError) => { + Logger.info(`[RTNPdfView]: error downloadHttpFile`); + this.onError(err.message); + reject("downloadFile failed" + err.message); + }); + } catch (error) { + Logger.info(`[RTNPdfView]: error downloadHttpFile`); + let err: BusinessError = error as BusinessError; + this.onError(err.message); + reject("downloadFile failed" + err.message); + } + }); + } + + async processFileName(originalURL: string): Promise { + return new Promise(async (resolve, reject) => { + let mdAlgName = 'SHA256'; // 摘要算法名 + let md = cryptoFramework.createMd(mdAlgName); + try { + await md.update({ data: new Uint8Array(buffer.from(originalURL, 'utf-8').buffer) }); + let mdResult: cryptoFramework.DataBlob = await md.digest(); + const fileName: string = buffer.from(mdResult.data).toString('hex'); + Logger.info(`[RTNPdfView]: fileName: ${fileName}`); + resolve(fileName); + } catch (e) { + this.onError(e.message); + reject("processFileName error"); + } + }); + } + + async sendToSandBox(assetPath: string): Promise { + assetPath = assetPath!.replace("asset://", "assets/"); + Logger.info(`[RTNPdfView]: load pdf assetPath: ${assetPath}`); + const newFileName: string = await this.processFileName(assetPath); + + // 需将pdf文档复制到沙箱路径下 + let context = getContext() as common.UIAbilityContext; + let content = context.resourceManager.getRawFileContentSync(`${assetPath}`); // pdf_reference + let dir = context.filesDir; + let fileFullPath = dir + '/' + newFileName + '.pdf'; + Logger.info(`[RTNPdfView]: load pdf filePath: ${fileFullPath}`); + let res = fs.accessSync(fileFullPath); + Logger.info(`[RTNPdfView]: fs accessSync: ${res}`); + let fdSand = fs.openSync(fileFullPath, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC); + fs.writeSync(fdSand.fd, content.buffer); + fs.closeSync(fdSand.fd); + return fileFullPath; + } + + async updateSource() { + let src: string | undefined = this.descriptor.rawProps.path; + Logger.info(`[RTNPdfView]: src is ${src}`); + if (src) { + if (src!.startsWith('http://') || src!.startsWith('https://')) { + this.doLoadHttpDocument(src); // 适配加载在线文件 + } else { + let isDeleteOriginFile: boolean = false; + if (src!.startsWith('asset://')) { // 适配require('../assets/test.pdf') + src = await this.sendToSandBox(src); + isDeleteOriginFile = true; + } + this.doLoadLocalDocument(src, isDeleteOriginFile); // 适配本地其他目录,比如cache + } } } aboutToAppear() { - this.descriptor = this.ctx.descriptorRegistry.getDescriptor(this.tag) + this.descriptor = this.ctx.descriptorRegistry.getDescriptor(this.tag); this.updateSource(); this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, (newDescriptor) => { @@ -85,7 +277,6 @@ export struct RTNPdfView { this.updateSource(); } ) - this.cleanupCommandCallback = this.ctx.componentCommandReceiver.registerCommandCallback( this.tag, (command, args: (boolean | number)[]) => { @@ -97,18 +288,42 @@ export struct RTNPdfView { aboutToDisappear() { this.cleanupCommandCallback?.(); this.unregisterDescriptorChangesListener?.(); - if (this.controllerStatus) { - this.webviewController.removeCache(true); - } } - loadComplete(numberOfPages: number) { + loadComplete(numberOfPages: number, filePath: string) { Logger.info("[RNOH]:enter loadComplete"); + + // 获取页面宽度、高度 this.ctx.rnInstance.emitComponentEvent( this.descriptor.tag, PDF_VIEW_TYPE, { - message: `${LOAD_COMPLETE}|${numberOfPages}|${this.width}|${this.height}` + type: 'onChange', + message: `loadComplete|${numberOfPages}|100|100` + } + ); + } + + loadProgress(percent: number) { + Logger.info("[RNOH]:enter loadProgress"); + this.ctx.rnInstance.emitComponentEvent( + this.descriptor.tag, + PDF_VIEW_TYPE, + { + type: 'onChange', + message: `loadProgress|${percent}` + } + ); + } + + onError(e: string) { + Logger.info("[RNOH]:enter onError"); + this.ctx.rnInstance.emitComponentEvent( + this.descriptor.tag, + PDF_VIEW_TYPE, + { + type: 'onChange', + message: `error|${e}` } ); } @@ -119,18 +334,32 @@ export struct RTNPdfView { this.descriptor.tag, PDF_VIEW_TYPE, { - message:`${PAGE_CHANGE}|${page}|${numberOfPages}` + type: 'onChange', + message: `pageChanged|${page}|${numberOfPages}` + } + ); + } + + onPressLink(url: string) { + Logger.info("[RNOH]:enter onPressLink"); + this.ctx.rnInstance.emitComponentEvent( + this.descriptor.tag, + PDF_VIEW_TYPE, + { + type: 'onChange', + message: `linkPressed|${url}` } ); } - onScaleChanged() { + onScaleChanged(currentScale: number) { Logger.info("[RNOH]:enter scaleChanged"); this.ctx.rnInstance.emitComponentEvent( this.descriptor.tag, PDF_VIEW_TYPE, { - message: `${SCALE_CHANGED}|${this.scale}` + type: 'onChange', + message: `scaleChanged|${currentScale}` } ); } @@ -141,37 +370,23 @@ export struct RTNPdfView { this.descriptor.tag, PDF_VIEW_TYPE, { + type: 'onChange', message: ON_PROGRESS_CHANGE } ); } - private getSrc(): string { - if (this.source) { - return this.isHttpUrl(`${this.source}`) ? `${this.source}${HIDE_TOOLBAR}` : `${this.source}` - } - return this.isHttpUrl(`${this.descriptor.rawProps.path}`) ? - `${this.descriptor.rawProps.path}${HIDE_TOOLBAR}` : `${this.descriptor.rawProps.path}` - } - - private isHttpUrl(url: string | undefined): boolean { - if (url) { - return [HTTP, HTTPS].some((item: string) => url.startsWith(item)) - } - return false - } - build() { RNViewBase({ ctx: this.ctx, tag: this.tag }) { if (this.descriptor.rawProps.path) { - Web({ src: this.getSrc(), controller: this.webviewController }) - .domStorageAccess(true) - .javaScriptAccess(true) - .width('100%') - .height('100%') - .onControllerAttached(() => { - this.controllerStatus = true - }) + PDFView( + { + controller: this.pdfController, + pageLayout: pdfService.PageLayout.LAYOUT_SINGLE, + isContinuous: true, + pageFit: pdfService.PageFit.FIT_WIDTH + } + ) } } } diff --git a/index.js b/index.js index e0ffdcf6..5250a7c4 100644 --- a/index.js +++ b/index.js @@ -203,7 +203,7 @@ export default class Pdf extends Component { const filename = source.cacheFileName || SHA1(uri) + '.pdf'; console.log('=== _prepareFile filename: ' + filename); - // const cacheFile = ReactNativeBlobUtil.fs.dirs.CacheDir + '/' + filename; + const cacheFile = '' // delete old cache file this._unlinkFile(cacheFile); @@ -211,30 +211,8 @@ export default class Pdf extends Component { if (isNetwork) { this._downloadFile(source, cacheFile); } else if (isAsset) { - // ReactNativeBlobUtil.fs - // .cp(uri, cacheFile) - // .then(() => { - // if (this._mounted) { - // this.setState({ path: cacheFile, isDownloaded: true, progress: 1 }); - // } - // }) - // .catch(async (error) => { - // this._unlinkFile(cacheFile); - // this._onError(error); - // }) } else if (isBase64) { let data = uri.replace(/data:application\/pdf;base64,/i, ''); - // ReactNativeBlobUtil.fs - // .writeFile(cacheFile, data, 'base64') - // .then(() => { - // if (this._mounted) { - // this.setState({ path: cacheFile, isDownloaded: true, progress: 1 }); - // } - // }) - // .catch(async (error) => { - // this._unlinkFile(cacheFile); - // this._onError(error) - // }); } else { if (this._mounted) { this.setState({ @@ -385,6 +363,8 @@ export default class Pdf extends Component { }, tableContents ); + } else if (message[0] === 'loadProgress') { + this.props.onLoadProgress && this.props.onLoadProgress(Number(message[1])); } else if (message[0] === 'pageChanged') { this.props.onPageChanged && this.props.onPageChanged(Number(message[1]), Number(message[2])); } else if (message[0] === 'error') { @@ -452,8 +432,8 @@ export default class Pdf extends Component { } else if (Platform.OS === "harmony") { console.log("===react-native-pdf style: " + JSON.stringify(this.props.style)); return ( - - + + ); } else { diff --git a/package.json b/package.json index add7301d..727090d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-oh-tpl/react-native-pdf", - "version": "6.7.4-0.2.2", + "version": "6.7.4-0.2.7-beta", "summary": "A react native PDF view component", "description": "A react native PDF view component, support ios and android platform", "main": "index.js",