一、背景
由于目前鸿蒙开发生态还没起来,也许在你看到这篇文章时已经有了更好更新的解决方案。
截止到2024.5.24 ,目前在鸿蒙Developer Preview2设备上加载出pdf的方案实在有限。
1. 使用原生web组件直接加载
这种方案不管是在线pdf,还是包内本地pdf资源还是包外沙箱路径pdf都是可以的。
但由于其功能和样式的自定义程度受限,所以这种方案目前的优先级不高。
2. 使用第三方库pdfviewer实现
OpenHarmony-TPC/pdfViewer (gitee.com)
3. 使用pdf.js实现
这种方案是我比较推荐的,因为pdf.js足够强大。
目前的实现可以基于这个项目 hm-pdf-viewer: harmony pdfviewer (gitee.com)
这个项目的实现非常好,但是经过验证,这个基于pdf.js的项目只支持包内本地pdf和在线pdf链接。对于沙箱的pdf是无法加载的。接下来,就解决一下这个问题。
二、如何使用pdf.js加载沙箱路径的pdf文件?
1. 发现问题
经过验证,点击pdf.js工具栏的打开按钮 ,然后通过web组件的onShowFileSelector方法可以监听到h5的input file,从而调起鸿蒙本机的文件管理器用于选择文件。然后这些选择的文件其实就是沙箱路径。然后稍作修改,不需要调起文件管理器,直接把沙箱路径的pdf链接通过handleFileList方法给h5搞过去就行了。然后如图所示就打开了。
.onShowFileSelector((event) => {
if (event) {
event.result.handleFileList([this.url]);
console.log('url' + this.url)
}
return true
})
那么问题就来了,不可能让用户手动点吧 ?所以需要考虑在页面加载时触发这个input file 事件。
但是这样显然是行不通的,不信你可以自己试试。
由于h5内核限制,不可能通过代码比如 dom 的 click( )方法或者其他鼠标单击事件自动触发文件选择器。 具体可参考
https://github.com/lostvita/blog/issues/32https://github.com/lostvita/blog/issues/32
难道问题无法解决了吗?
2. 解决
由于无法自动触发input file 事件,那么就需要考虑如何绕开它。
经过分析源码(viewer.js),发现在文件选择完成后其实 是通过选择的File对象构造一个url然后打开就行了。
那么思路就来了,我们只需要把沙箱路径的pdf 构造成h5的File对象就行了。
然后我首先通过runJavaScript方法调用在viewer.html的script标签里自定义的openFIle函数,企图把鸿蒙读取的文件对象直接传过去。
然后发现其实会报错,因为这个fs.File对象不能够被h5的js识别。
最后想到了Base64字符串。
因为字符串是通用类型,可以直接当成参数传给h5。只需要在鸿蒙端把沙箱pdf转成base64,然后传给h5,然后h5 在把base64构造成FIle对象即可打开。
import { util } from '@kit.ArkTS';
import fs from '@ohos.file.fs';
export interface IBase64 {
base64: string;
fileName: string;
}
export function sandBoxPdfToBase64(url: string) {
let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件
let stat = fs.statSync(url); // 获取文件状态
console.info("stat.size:" + stat.size) // 打印文件的长度
let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象
let base64 = new util.Base64Helper(); // 实例化Base64Helper
let num = fs.readSync(file.fd, buf); // 读取文件
let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) // 转换成Uint8Array
console.info(`data长度:${data.length}`)
console.info(`data:${data}`)
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64
let fileName = file.name
//console.info("file fd: " + file.fd);
fs.closeSync(file);
//return new Map().set('base64', retStr).set('fileName', fileName) as Map<string, string>;
return { base64: retStr, fileName: fileName } as IBase64;
}
然后在viewer.html里把base64转成File对象
<script type="text/javascript">
function base64ToBlob(base64) {
var bstr = atob(base64),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: "application/pdf",
});
}
function blobToFile(blob, fileName) {
return new File([blob], fileName);
}
function openFile(code, fileName) {
//base64转File对象
let blob = base64ToBlob(code)
let file = blobToFile(blob, fileName)
PDFViewerApplication.open({
url: URL.createObjectURL(file),
originalUrl: file.name
});
}
</script>
请注意标签位置要放在引入view.js的标签之后
最终就能在页面加载时自动加载沙箱pdf 啦
三、关于定制化
业务需要定制化的话可以去viewer.css 和 viewer.js里修改。
不需要哪个按钮直接在类名加个hidden即可,不要直接删除,否则会导致错误。
由于代码太多太散,这里不方便展示,所以定制化的话需要自己去研究了,还是相对比较容易的。
这里展示一下效果图仅供参考,起码知道可实现什么效果。
修改前 :
修改后 :