前端使用vue-pdf、pdf-lib、canvas 给PDF文件添加水印,并预览与下载

前端使用vue-pdf、pdf-lib 给pdf添加水印,并预览与下载

  • 效果预览
  • 使用第三方插件
    • 安装依赖插件
    • import 导入依赖
  • 预览添加水印的pdf
  • 下载添加水印的pdf
  • 预览及下载总结
  • 完整代码

效果预览

前端使用vue-pdf、pdf-lib、canvas 给PDF文件添加水印,并预览与下载_第1张图片

使用第三方插件

安装依赖插件

npm i vue-pdf --save
npm i pdf-lib --save
npm install --save @pdf-lib/fontkit  //为 pdf-lib 加载自定义字体的工具

import 导入依赖

import pdf from "vue-pdf";
import { degrees, PDFDocument, rgb, StandardFonts } from "pdf-lib";
import fontkit from "@pdf-lib/fontkit"; 

预览添加水印的pdf

setWatermarkContent() {
  let ele = document.createElement("canvas");
  ele.width = 250;
  ele.height = 200;
  let objmsg = {
    canvas: ele,
    fontText: "张三-2023-01-01",
    fontSize: 20,
    fontFamily: "microsoft yahei",
    fontcolor: "#dadbdc", //字体颜色   默认 #dadbdc
    rotate: 25, //旋转角度   数字类型
    textAlign: "left", //水印文字居中方式:left center right  默认 left
  };
  this.createWaterMark(objmsg);
  this.drawWaterMark(ele);
},
// 创建canvas水印图片
createWaterMark({ canvas, fontText, fontFamily = "microsoft yahei", fontSize = 30, fontcolor = "#dadbdc", rotate = 30, textAlign = "left" }) {
  let ctx = canvas.getContext("2d");
  ctx.font = `${fontSize}px ${fontFamily}`;
  ctx.rotate((-rotate * Math.PI) / 180);
  ctx.fillStyle = fontcolor;
  ctx.textAlign = textAlign;
  ctx.textBaseline = "Middle";
  ctx.fillText(fontText, canvas.width / 6, canvas.height / 2);
},
// 给pdf增加水印遮罩层
drawWaterMark(ele) {
  let div = document.createElement("div");
  div.style.pointerEvents = "none";
  div.style.top = "0";
  div.style.left = "0px";
  div.style.position = "absolute";
  div.style.background = "url(" + ele.toDataURL("image/png") + ") left top repeat";
  let width = document.getElementById("pdfBox").clientWidth || 700;
  let height = document.getElementById("pdfBox").clientHeight || 700;
  div.style.width = width + "px";
  div.style.height = height + "px";
  document.getElementById("myIframe").appendChild(div);
},

下载添加水印的pdf

原理就是给显示pdf 的容器增加一层水印遮罩层


// 处理PDF
async downFile() {
  /*2.获取pdf文件的arrarybuffer文件流
  可请求后台接口返回的base64文件流,然后转成arrayBuffer类型
  可访问前端项目中的本地文件,不能直接访问服务器链接文件,会有跨域问题*/
  try {
    // 1.通过url获取pdf文件的arrarybuffer文件流
    const existingPdfBytes = await fetch(this.fileUrl).then((res) => res.arrayBuffer());
    // 2.将arraybuffer数据转成pdf文档
    const pdfDoc = await PDFDocument.load(existingPdfBytes);
    // 3.1  内置字体(不支持中文), 如果水印中不包含中文可直接用内置字体(不支持中文)
    // const fontkitFile = await pdfDoc.embedFont(StandardFonts.Helvetica);
    // 3.2 自定义字体,如不需要使用自定义字体可以将这一段全部注释掉,也不用下载自定义字体文件和自定义字体工具fontkit
    // 将自己下载好的.ttf文件放置项目中,然后访问文件路径(不支持访问本地文件)
    const fontBytes = await fetch("/fonts/SourceHanSansCN-Normal.ttf").then((res) => res.arrayBuffer());
    pdfDoc.registerFontkit(fontkit); // 自定义字体挂载、fontkit为自定义字体注册工具
    const fontkitFile = await pdfDoc.embedFont(fontBytes);
    //  4. 为每页pdf添加文字水印
    const pages = pdfDoc.getPages();
    for (let i = 0; i < pages.length; i++) {
      const noPage = pages[i];
      const { width, height } = noPage.getSize();
      for (let i = 0; i < 10; i++) {
        for (let j = 0; j < 3; j++) {
          noPage.drawText("张三-2023-01-01", {
            x: 230 * j,
            y: (height / 4) * i,
            size: 20,
            font: fontkitFile, //字体(内置/自定义)
            color: rgb(0.46, 0.53, 0.6),
            rotate: degrees(45),
            opacity: 0.3,
          });
        }
      }
    }
    //5. 保存pdf文件的unit64Arrary文件流
    const pdfBytes = await pdfDoc.save();
    this.saveByteArray(this.waterFile.fileName + ".pdf", pdfBytes);
  } catch (error) {
    this.$message.warning("文件下载失败!");
  }
},
// 下载文件
saveByteArray(reportName, byte) {
  var blob = new Blob([byte], { type: "application/pdf" });
  var link = document.createElement("a");
  link.href = window.URL.createObjectURL(blob);
  var fileName = reportName;
  link.download = fileName;
  link.click();
},

预览及下载总结

下载:

  1. 通过url获取pdf文件的arrarybuffer文件流
  2. 将arraybuffer数据转成pdf文档
  3. 添加水印字体(内置/自定义)
  4. 为每页pdf添加文字水印
  5. 保存pdf文件的unit64Arrary文件流

预览:

  1. 创建canvas容器(用于显示水印文字)
  2. 创建水印canvas
  3. 将水印canvas遮罩层定位到pdf容器中

完整代码

<template>
  <div>
    <div class="content">
      <div id="myIframe" style="max-width: 700px; min-height: 550px; position: relative; margin: 0 auto">
        <pdf id="pdfBox" :page="pageNum" :src="fileUrl" @progress="loadedRatio = $event" @num-pages="totalPages = $event"></pdf>
      </div>
      <el-button v-if="false" type="primary" @click="downFile" plain style="width: 300px">保存并下载pdf</el-button>
    </div>
    <span slot="footer" class="dialog-footer">
      <div class="btnGroup" v-if="totalPages">
        <div class="pageNum">{{ pageNum }} / {{ totalPages }}</div>
        <el-button-group>
          <el-button round plain type="primary" icon="el-icon-arrow-left" size="mini" @click="prePage">上一页</el-button>
          <el-button round plain type="primary" size="mini" @click="nextPage">下一页<i class="el-icon-arrow-right el-icon--right"></i></el-button>
        </el-button-group>
      </div>
    </span>
  </div>
</template>

<script>
/* npm i vue-pdf --save
npm install --save @pdf-lib/fontkit
npm i pdf-lib --save
*/

import pdf from "vue-pdf";
import { degrees, PDFDocument, rgb, StandardFonts } from "pdf-lib";
import fontkit from "@pdf-lib/fontkit"; //为 pdf-lib 加载自定义字体的工具

export default {
  components: {
    pdf,
  },
  data() {
    return {
      pageNum: 1, //显示第一页
      loadedRatio: 0, // 当前页面的加载进度,范围是0-1 ,等于1的时候代表当前页已经完全加载完成了
      totalPages: 0, //pdf总页数
      fileUrl:"XXXXX.pdf",
    };
  },

  mounted() {
    this.getPageNum();
  },
  methods: {
   // 获取PDF总页数
    getPageNum() {
      let loadingTask = pdf.createLoadingTask(this.fileUrl);
      loadingTask.promise
        .then((pdf) => {
          this.totalPages = pdf.numPages;
          this.$nextTick(() => {
            this.setWatermarkContent();
          });
        })
        .catch((err) => {
          this.$message.warning("pdf加载失败");
        });
    },
    // 上一页
    prePage() {
      let page = this.pageNum;
      page = page > 1 ? page - 1 : this.totalPages;
      this.pageNum = page;
      window.scrollTo(0, 0);
    },

    // 下一页
    nextPage() {
      let page = this.pageNum;
      page = page < this.totalPages ? page + 1 : 1;
      this.pageNum = page;
      window.scrollTo(0, 0);
    },

    setWatermarkContent() {
      // 1.创建canvas容器(用于显示水印文字)
      let ele = document.createElement("canvas");
      ele.width = 250;
      ele.height = 200;
      let objmsg = {
        canvas: ele,
        fontText: "张三-2023-01-01", // String
        fontSize: 20,
        fontFamily: "microsoft yahei",
        fontcolor: "#dadbdc", //字体颜色   默认 #dadbdc
        rotate: 25, //旋转角度   数字类型
        textAlign: "left", //水印文字居中方式:left center right  默认 left
      };
      // 2.创建水印canvas
      this.createWaterMark(objmsg);
       // 2.将水印canvas遮罩层定位到pdf容器中
      this.drawWaterMark(ele);
    },
    // 创建canvas水印图片
    createWaterMark({ canvas, fontText, fontFamily = "microsoft yahei", fontSize = 30, fontcolor = "#dadbdc", rotate = 30, textAlign = "left" }) {
      let ctx = canvas.getContext("2d");
      ctx.font = `${fontSize}px ${fontFamily}`;
      ctx.rotate((-rotate * Math.PI) / 180);
      ctx.fillStyle = fontcolor;
      ctx.textAlign = textAlign;
      ctx.textBaseline = "Middle";
      ctx.fillText(fontText, canvas.width / 6, canvas.height / 2);
    },
    // 给pdf增加水印遮罩层
    drawWaterMark(ele) {
      let div = document.createElement("div");
      div.style.pointerEvents = "none";
      div.style.top = "0";
      div.style.left = "0px";
      div.style.position = "absolute";
      div.style.background = "url(" + ele.toDataURL("image/png") + ") left top repeat";
      let width = document.getElementById("pdfBox").clientWidth || 700;
      let height = document.getElementById("pdfBox").clientHeight || 700;
      div.style.width = width + "px";
      div.style.height = height + "px";
      document.getElementById("myIframe").appendChild(div);
    },

    // PDF 下载
    async downFile() {
      /*2.获取pdf文件的arrarybuffer文件流
       可请求后台接口返回的base64文件流,然后转成arrayBuffer类型
       可访问前端项目中的本地文件,不能直接访问服务器链接文件,会有跨域问题*/
      try {
        // 1.通过url获取pdf文件的arrarybuffer文件流
        const existingPdfBytes = await fetch(this.fileUrl).then((res) => res.arrayBuffer());
        // 2.将arraybuffer数据转成pdf文档
        const pdfDoc = await PDFDocument.load(existingPdfBytes);
        // 3.1  内置字体(不支持中文), 如果水印中不包含中文可直接用内置字体(不支持中文)
        // const fontkitFile = await pdfDoc.embedFont(StandardFonts.Helvetica);
        // 3.2 自定义字体,如不需要使用自定义字体可以将这一段全部注释掉,也不用下载自定义字体文件和自定义字体工具fontkit
        // 将自己下载好的.ttf文件放置项目中,然后访问文件路径(不支持访问本地文件)
        const fontBytes = await fetch("/fonts/SourceHanSansCN-Normal.ttf").then((res) => res.arrayBuffer());
        pdfDoc.registerFontkit(fontkit); // 自定义字体挂载、fontkit为自定义字体注册工具
        const fontkitFile = await pdfDoc.embedFont(fontBytes);
        //  4. 为每页pdf添加文字水印
        const pages = pdfDoc.getPages();
        for (let i = 0; i < pages.length; i++) {
          const noPage = pages[i];
          const { width, height } = noPage.getSize();
          for (let i = 0; i < 10; i++) {
            for (let j = 0; j < 3; j++) {
              noPage.drawText('张三-2023-01-01', {
                x: 230 * j,
                y: (height / 4) * i,
                size: 20,
                font: fontkitFile, //字体(内置/自定义)
                color: rgb(0.46, 0.53, 0.6),
                rotate: degrees(45),
                opacity: 0.3,
              });
            }
          }
        }
        //5. 保存pdf文件的unit64Arrary文件流
        const pdfBytes = await pdfDoc.save();
        this.saveByteArray( "水印PDF.pdf", pdfBytes);
      } catch (error) {
        this.$message.warning("文件下载失败!");
      }
    },
    // 下载文件
    saveByteArray(fileName, byte) {
      var blob = new Blob([byte], { type: "application/pdf" });
      var link = document.createElement("a");
      link.href = window.URL.createObjectURL(blob);
      var fileName = reportName;
      link.download = fileName;
      link.click();
    },
  },
};
</script>

你可能感兴趣的:(知识点(小记),工具,前端,vue.js,pdf)