utools插件开发-图片批量转PDF格式
应之前一个txt转word
插件下的评论一个需求,顺手做个转换插件。
📌 分析:
图片类型有很多,但常见的图片格式一般只是:png
|jpg
|gif
|jpeg
|webp
,所以本次只对该几种格式进行转换(其他类型其实也不适合转pdf🙃)
根据用户的需求可以猜想出的转换可能会分为三种:单图转换、多图转换、多图合并转换。
前期思路:需要将用户选中的图片格式,进行路径读取,使用canvas进行转换,然后输出为PDF格式。
后面感觉这样效率会不会太低,直接用第三方库不香嘛,
🔍 寻找轮子
找了几个都具有该功能的第三方库,所以确定了该需求还是可以做的(没有什么靠轮子搞不定的😷)
看到其他库的话差不多都是封装这个库来使用,最终选择了jspdf
。
这个是官方demo
,方便我们调试查看效果jsPDF - Create PDFs with HTML5 JavaScript Library (mrrio.github.io)
✍ 代码编写
因jspdf
对图片处理也都是先将图片转为base64进行处理,不管是传入图片路径,或者网络路径,都是先转为base64。
将图片转为base64格式
const getImageBase64 = (imgPath, fileName)=> {
let data = fs.readFileSync(imgPath, 'binary')
const buffer = new Buffer(data, 'binary');
let img64 = 'data: image/' + getImageType(fileName) + ';base64,' + buffer.toString('base64');
return img64 || ""
}
判断图片的格式
转为base64需要头部加入图片的格式,如:data:image/png
const isImage = str => {
var reg = /\.(png|jpg|gif|jpeg|webp)$/;
return reg.test(str);
}
🧭 对jsPDF进行封装
const { jsPDF } = require("./jspdf.umd.min.js"); //将jspdf下载至本地,不知道怎么回事,用npm下载的无法导入使用
// A4纸宽高
const a4Page = {
width: 595.28,
height: 841.89
}
let pdf
let fontFamily = 'Arial'
// pdf页面间距
let padding = {
width: 20,
height: 25
}
// 生成的pdf的宽高
let pdfPage = {}
// pdf插入内容的位置
let pdfPostion = 0
const init = options => {
if (options && typeof options.initFont === 'function') {
fontFamily = options.initFont(jsPDF.API)
}
pdf = new jsPDF('', 'pt', 'a4')
if (options.pagePadding) {
Object.assign(padding, options.pagePadding)
}
pdfPage = {
width: a4Page.width - padding.width * 2,
height: a4Page.height - padding.height * 2
}
pdfPostion = padding.height
}
// 插入图片
const addImage = img => {
const imgPage = {
width: pdfPage.width,
height: img.height / img.width * pdfPage.width
}
console.log(imgPage.height)
if (imgPage.height > pdfPage.height) { // 图片高于一页pdf高度时,将图片截断生成多页pdf
let _pdfPostion = pdfPostion
let leftImgHeight = imgPage.height
while (leftImgHeight > 0) {
pdf.addImage(img.data, 'png', padding.width, _pdfPostion, imgPage.width, imgPage.height)
_pdfPostion -= a4Page.height;
leftImgHeight = imgPage.height + _pdfPostion;
if (leftImgHeight > 0) {
pdf.addPage();
pdfPostion = leftImgHeight
}
}
} else { // 否则将图片插入到pdf中,若pdf剩余高度不足以插入整张图时,新增一页并将插入位置重置
if (pdfPostion + imgPage.height > pdfPage.height) {
pdf.addPage()
pdfPostion = padding.height
}
pdf.addImage(img.data, 'png', padding.width, pdfPostion, imgPage.width, imgPage.height)
pdfPostion += imgPage.height
}
}
// 插入文字
const addText = text => {
let { fontSize = 16, spacing = 5, textIndent = 0 } = text.options
if (pdfPostion + spacing > pdfPage.height) {
pdf.addPage()
pdfPostion = padding.height
}
const splitLength = pdfPage.width - textIndent
const words = pdf.setFont(fontFamily)
.setFontSize(fontSize)
.splitTextToSize(text.data, splitLength)
pdf.text(padding.width + textIndent, pdfPostion + fontSize + spacing, words)
pdfPostion += fontSize * words.length + spacing * 2
}
const addPage = () => {
pdf.addPage()
pdfPostion = padding.height
}
/**
* base64图片数组生成pdf
* @param {*} images base64图片数组
* @param {*} title 下载pdf文件的名称
* @param {*} options 配置信息
*/
const savePdf = (images, title, options) => {
images.forEach(img => {
switch (img.type) {
case 'page':
addPage()
break
case 'image':
addImage(img)
break
case 'text':
addText(img)
break
default:
break
}
})
return pdf.save(`${title}.pdf`, {
returnPromise: true
})
}
/**
* 图片转为base64信息
* img: 图片dom,设置图片最大宽高,以防渲染出的图片体积过大
*/
const getBase64Image = img => {
if (img.width > 1000) {
img.height = img.height / img.width * 1000
img.width = 1000
}
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext("2d")
ctx.drawImage(img, 0, 0, img.width, img.height)
return canvas.toDataURL('image/png')
}
/**
* 处理src图片转为base64并执行生成pdf
* @param {*} images 图片数组,可为 base64 信息,或 src 地址
* @param {*} title 下载pdf文件的名称
* @param {*} options 配置信息
*/
const imagePdf = (images, title = 'Oct1a', options = {}) => {
init(options)
let newImages = []
let index = 0
const formatImages = (index, resolve, reject) => {
if (index < images.length) {
const img = images[index]
let _img = {
type: img.type || 'image',
data: img.data || '',
width: img.width || 100,
height: img.height || 100,
options: img.options || {}
}
if (_img.type === 'image' && _img.data.indexOf('data:image/') === -1) {
const newImg = new Image()
newImg.onload = () => {
_img.data = getBase64Image(newImg)
_img.width = newImg.width
_img.height = newImg.height
newImages.push(_img)
formatImages(++index, resolve, reject)
}
newImg.onerror = () => {
formatImages(++index, resolve, reject)
}
newImg.setAttribute('crossOrigin', 'anonymous')
newImg.src = _img.data
} else {
newImages.push(_img)
formatImages(++index, resolve, reject)
}
} else {
savePdf(newImages, title, options)
.then(result => {
resolve(result)
})
.catch(error => {
reject(error)
})
}
}
const promise = new Promise((resolve, reject) => {
formatImages(index, result => {
resolve(result)
}, error => {
reject(error)
})
})
return promise
}
module.exports = {
imagePdf
}
🎶 主文件
const fs = require('fs');
const $path = require('path');
const { imagePdf } = require("./js/index.js");
🧩 批量对图片进行转换
循环选中文件,获取图片base64,传入封装的方法内。
* type 插入类型,image 图片类型,text 文字类型,page 新增一页。默认 image
* data 插入内容,image 时可为 base64 信息,或 src 地址;text 时为文字内容
* width 图片宽,图片信息为 src 时可不传
* height 图片高,
* options: { 文字配置信息,非必须
* fontSize: 文字大小, 默认16px
\* spacing: 间距,默认同5px \* textIndent: 文字缩进, 单位px,默认0
* }
let files = action.payload
// 遍历选中文件
for (const element of files) {
if (element.isFile) {
let { name, path } = element
let img64 = getImageBase64(path, name)
imagePdf([{
data: img64
}], name.slice(0, name.lastIndexOf('.'))) //文件名使用图片原名称切割获取
}
}
💦 批量将多个图片合成一个PDF
let files = action.payload
let images = []
// 遍历选中文件
for (const element of files) {
if (element.isFile) {
let { name, path } = element
let img64 = getImageBase64(path, name)
images.push({
data: img64
})
}
}
imagePdf(images, new Date().getTime()) //多张图片合并就直接使用时间戳作为文件名了。
💕 感谢
hollton/image-pdf: image to pdf 图片转为pdf文件 (github.com)
[jspdf - npm (npmjs.com)]
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。