nextjs项目使用react-pdf生成PDF文件遇到的问题
Error: render is a Node specific API
我想要做一个功能,点击按钮时可以触发生成pdf文件并下载,所以觉得可以调用下面的js去执行
ReactPDF.render(<Pdf data={data}/>, `${__dirname}/example.pdf`)
结果就是报错了
render这个方法是一个node api,看来只能在服务侧运行了,我不想这么干
Error: PDFDownloadLink is a web specific API
所以我又尝试了PDFDownloadLink,用如下的代码
<PDFDownloadLink
document={<Pdf data={data}/>}
fileName='somename.pdf'
>
{({ loading }) => (loading ? 'Loading document...' : 'Download now!')}
</PDFDownloadLink>
结果又报错了
PDFDownloadLink是一个web api只能在浏览器端运行,原来nextjs的页面是在服务端渲染的,所以才报了这个错,而上面的render方法却是在页面上按钮触发时调用的,所以报了那个错。
所以,要让PDFDownloadLink在页面返回浏览器之后再渲染,于是有了下面的代码
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
...
{
isClient ? (
<PDFDownloadLink
document={<Pdf data={data}/>}
fileName='somename.pdf'
>
{({ loading }) => (loading ? 'Loading document...' : 'Download now!')}
</PDFDownloadLink>
) : null
}
...
isClient在页面渲染完之后置为TRUE,这时候才去渲染PDFDownloadLink
可能这个方案有时候觉得还不够完美,那么可以使用另一个方案,那就是动态加载PDFDownloadLink,设置ssr=false,这个方法相比判断isClient似乎更加优雅一点
将PDFDownloadLink放在pdf组件中,整个组件需要动态引入
import { PDFDownloadLink } from "@react-pdf/renderer"
import Pdf from "."
const DyPdf = ({ ...props }) => {
return (
<PDFDownloadLink
document={<Pdf data={props.data} />}
fileName='somename.pdf'
>
{({ loading }) => (loading ? 'Loading document...' : 'Download now!')}
</PDFDownloadLink>
)
}
export default DyPdf
在使用的地方动态引入,就可以当作普通标签正常使用了
const DyPdf = dynamic(() => import("components/DyPdf"), {
loading: () => <p>Loading...</p>,
ssr: false,
});
...
<DyPdf data={data}></DyPdf>
中文乱码问题
这个需要register字体,将字体文件放在项目里,调用Font.register
字体文件在我们电脑上就可以找到,我的字体文件在C:\Windows\Fonts
最好是找*.ttf文件,因为我试了*.ttc文件不行,可能是兼容性问题
我这个是仿宋,我是放在pdf模板文件中调用的,下面就可以使用这个字体了
Font.register({
family: 'fangsong',
src: '/fonts/simfang.ttf'
});
自动换行问题
对于英文文档,使用css样式就可以解决自动换行的问题了,但是对于中文却不能,这里增加一个分词器,就可以完美解决这个问题,我也放在了pdf模板文件里
Font.registerHyphenationCallback((word: string) => {
if (word.length === 1) {
return [word]
}
return Array.from(word).flatMap(char => {
const c = char.charCodeAt(0)
// 判断如果是中文,就在前后增加空字符串(注意是空字符串,不是空格)
if (c >= 0x4E00 && c <= 0x9FA5) {
return ['', char, '']
}
return [char]
})
})
总结
以上,这些问题的解决方法与思路,当然不是我自己摸黑摸索出来的,要感谢强大的网友,当时搜索过的链接没有仔细保存,只能是万分感谢了,不过我还是进行了整合还有改进的,当时搜索的时候网友没有描述清楚的,我也具体化了,哈哈,我还是有功劳的