Oct1a

utools插件开发-图片批量转PDF格式

image-20210810112909768

应之前一个txt转word插件下的评论一个需求,顺手做个转换插件。

📌 分析:

image-20210810113743436

图片类型有很多,但常见的图片格式一般只是:png|jpg|gif|jpeg|webp,所以本次只对该几种格式进行转换(其他类型其实也不适合转pdf🙃)

根据用户的需求可以猜想出的转换可能会分为三种:单图转换、多图转换、多图合并转换。

前期思路:需要将用户选中的图片格式,进行路径读取,使用canvas进行转换,然后输出为PDF格式。

后面感觉这样效率会不会太低,直接用第三方库不香嘛,

🔍 寻找轮子

找了几个都具有该功能的第三方库,所以确定了该需求还是可以做的(没有什么靠轮子搞不定的😷)

image-pdf - npm (npmjs.com)

jspdf - npm (npmjs.com)

pdf-image - npm (npmjs.com)

看到其他库的话差不多都是封装这个库来使用,最终选择了jspdf

这个是官方demo,方便我们调试查看效果jsPDF - Create PDFs with HTML5 JavaScript Library (mrrio.github.io)

image-20210810133320948

✍ 代码编写

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 国际许可协议 进行许可。