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

news2024/9/21 18:30:24

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

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

效果预览

在这里插入图片描述

使用第三方插件

安装依赖插件

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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java之面向对象基础

1.类和对象1.1什么是对象万物皆对象&#xff0c;只要是客观存在的事物都是对象1.2什么是面向对象1.3什么是类类是对现实生活中一类具有共同属性和行为的事物的抽象类的特点&#xff1a;类是对象的数据类型类是具有相同属性和行为的一组对象的集合1.4什么是对象的属性属性&#…

微信小程序——使用npm包,安装 Vant weapp 组件库安装教程及使用vant组件

一.小程序对 npm 的支持与限制目前&#xff0c;小程序中已经支持使用 npm 安装第三方包&#xff0c;从而来提高小程序的开发效率。但是&#xff0c;在小程序中使用 npm 包有如下3个限制&#xff1a;&#x1f4dc;不支持依赖于 Node . js 内置库的包&#x1f4dc;不支持依赖于浏…

【软件测试】2023年的软件测试咋样?见鬼,我到底该如何进阶?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 一谈到进阶&#xf…

Sitecore本地安装详细介绍

一、简介 Sitecore 是一种 CMS(内容管理系统,位于 Web 前端和后端办公系统或流程之间的软件系统),本文已当前最新的 10.2.0 版本为例,介绍如何安装部署。 二、环境准备 Sitecore 依赖于 IIS、SQL Server,在后续 Sitecore 安装之前,这两依赖需要提前安装完成 2.1 II…

【CTF】ctf中用到的php伪协议总结及例题(持续更)

目录 前言 关于文件包含漏洞 php伪协议总结 关于php://协议 参考自&#xff1a; 前言 本篇文章使用的靶场是buuctf上的web题目&#xff1a;[BSidesCF 2020]Had a bad day 进行点击选项得到一个这样的url 这里猜测存在sql注入&#xff0c;没测出来。或者可能有php伪协议读…

excel函数应用:如何写出IF函数多级嵌套公式

说到函数就不得不提起函数中最受欢迎的三大家族&#xff1a;求和家族、查找引用家族、逻辑家族&#xff01;&#xff01;&#xff01;没错&#xff01;今天我们要介绍的就是三大家族之一逻辑函数家族的领头人&#xff1a;IF函数——很多人难以理解IF函数的多级嵌套使用。其实&a…

shell 函数详解

目录 函数 一&#xff0c;什么是函数 二&#xff0c; 函数的返回值 三&#xff0c;函数语法 示例1&#xff1a; 示例2&#xff1a; 四&#xff0c;函数的调用 示例1&#xff1a; 示例2&#xff1a; 五&#xff0c;函数库文件 六&#xff0c; 递归函数 示例1&#xf…

Node.js 全局对象介绍

在学习 Javascript 之初&#xff0c;会接触一个概念&#xff1a;JS 由三部分组成&#xff0c;DOM BOM ECMAScript。其中前两者是宿主环境&#xff0c;也就是浏览器所提供的能力。后者才是 JS 语言本身的标准。 在上篇文章《Node.js入门&#xff08;1&#xff09;&#xff1a…

SpringMVC之响应

目录 一&#xff1a;环境准备 二&#xff1a;响应页面[了解] 三&#xff1a;返回文本数据[了解] 四&#xff1a;响应JSON数据 SpringMVC接收到请求和数据后&#xff0c;进行一些了的处理&#xff0c;当然这个处理可以是转发给Service&#xff0c;Service层再调用Dao层完成的…

1月更新!EasyOps® 28+新功能“狂飙”上线~

2023节后&#xff0c;我们就要“搞事情”&#xff01; 一波新功能已上线&#xff0c;快看是不是你需要的&#xff01; 持续升级优化全平台产品&#xff0c; 只为成为你数字化变革最值得信赖的合作伙伴&#xff01; 优维EasyOps全平台28新功能来了&#xff01; ↓↓↓ 1、H…

Spring的后处理器之BeanFactoryPostProcessor

Spring的后处理器 Spring的后处理器是Spring对外开放的重要扩展点&#xff0c;允许我们介入到Bean的整个实例化流程中来&#xff0c;以达到动态注册BeanDefinition&#xff0c;动态修改BeanDefinition&#xff0c;以及动态修改Bean的作用。Spring主要有两种后处理器&#xff1…

【车载开发系列】StatusOfDTC的解析

【车载开发系列】StatusOfDTC的解析 StatusOfDTC的解析【车载开发系列】StatusOfDTC的解析StatusOfDTC概念StatusOfDTC列表StatusOfDTC状态掩码Bit 0: TestFailedBit 1: testFailedThisOperationCycleBit 2: pendingDTCBit 3: confirmedDTCBit 4: testNotCompletedSinceLastCle…

【操作系统】3、内存管理

文章目录三、内存管理3.1 内存基础3.1.1 内存管理概念3.1.2 程序装入与链接3.1.3 内存保护3.2 内存空间的分配与回收3.2.1 连续分配管理方式3.2.1.1 单一连续分配3.2.1.2 固定分区分配3.2.1.3 动态分区分配3.2.2 动态分区分配算法3.2.2.1 首次适应算法3.2.2.2 最佳适用算法3.2.…

【数据库原理与SQL Server应用】Part05——表和表数据操作

【数据库原理与SQL Server应用】Part05——表和表数据操作一、表概念1.1 表结构1.2 表类型1.3 数据类型二、创建表2.1 管理工具界面方式创建表2.2 命令行方式创建表三、修改表3.1 管理工具界面方式修改表3.2 命令行方式修改表四、删除表五、表数据操作5.1 管理工具界面方式操作…

怎么快速选择出色的香港服务器

相信一些大规模企业或站长都不满足于普通的香港VPS&#xff0c;虽然它也拥有很不错的性能与速度&#xff0c;但远远比不上香港服务器。但是&#xff0c;对于初次使用香港服务器的用户来说&#xff0c;选择起来肯定是要经过一番考虑的&#xff0c;那么&#xff0c;有没有什么简单…

电子词典项目

16. 电子词典项目需求 项目要求&#xff1a; 登录注册功能&#xff0c;不能重复登录&#xff0c;重复注册单词查询功能历史记录功能&#xff0c;存储单词&#xff0c;意思&#xff0c;以及查询时间基于TCP&#xff0c;支持多客户端连接采用数据库保存用户信息与历史记录将dic…

VS2022离线安装教程

官方教程下载和安装步骤 https://docs.microsoft.com/zh-cn/visualstudio/install/create-an-offline-installation-of-visual-studio?viewvs-2022 使用命令行创建本地布局 下载所需的 Visual Studio 版本的引导程序&#xff0c;并将其复制到要用作本地布局源位置的目录中。…

训练自己的GPT2模型(中文),踩坑与经验

GPT2与Bert、T5之类的模型很不一样&#xff01;&#xff01;&#xff01; 如果你对Bert、T5、BART的训练已经很熟悉&#xff0c;想要训练中文GPT模型&#xff0c;务必了解以下区别&#xff01;&#xff01;&#xff01; 官方文档里虽然已经有教程&#xff0c;但是都是英文&…

手撸低代码平台搭建(四)组件拖动自由布局的实现

前言 大家好,在前两篇文章中,我们走进了前端低代码的世界,并揭秘了低代码的核心——页面设计器的实现。在揭秘页面设计器时,我们重点分享了顺序排列布局的组件拖动方式,那篇文章的评论中,有小伙伴问到自由布局的实现,那么我们在这篇文章中来分享一下自由布局拖动的实现…

Echarts柱形头部圆弧处理

第008个点击查看专栏目录对于柱状图来说&#xff0c;我们想要的效果是圆柱的上面进行圆弧的处理&#xff0c;产生顺滑的感觉&#xff0c;怎么处理呢&#xff0c;只要设置好样式即可&#xff0c;参考源代码圆角半径&#xff0c;单位px&#xff0c;支持传入数组分别指定 4 个圆角…