pdf与canvas结合使用的场景1——为pdf增加水印


title: pdf与canvas结合使用的场景

date: 2024-08-28 10:03:45
tags: javascript

场景1:生成新的pdf水印

添加水印场景分析

这种情况下,不用再次封装好的一些水印工具的话,纯vue3前端的话就是使用pdfjs+canvas来生成。

思路:1.上传并读取pdf文件,上传水印内容

​ 2.将pdf文件使用pdfjs和canvas结合生成pdf的图片数组(将每一页生成为一个图片)

​ 3.使用canvas将图片画入画布中,然后再将水印内容画入画布中

​ 4.每生成一张带水印的图片,就将他写入新的pdf文件中

​ 5.所有图片添加水印并存入新的操作后保存

细节:1.同步与异步操作得注意,因为加载图片其实需要时间,得使用 async、await、promise等操作,来等待调取使用。2.得通过相关的可靠方法来计算水印位置这一操作。3.明确添加文字水印和添加图片水印的操作是不一样的。4.明确自己要一些什么效果比如旋转水印、文字水印颜色与透明度、图片水印的放大缩小、pdf页码加水印选择。依赖包引入

添加水印代码分析

1.上传并读取pdf文件

上传pdf按钮

vue 复制代码
<!-- 上传pdf按钮 -->
<el-button type="primary" plain>
 <label for="pdfUploader">上传PDF文件</label>
 </el-button>
 <input
    type="file"
    id="pdfUploader"
    @change="handleFileChange"
    accept="application/pdf"
    style="display: none"
         />

上传js代码,生成图片数组

javascript 复制代码
import * as pdfjsLib from "pdfjs-dist";
const handleFileChange = async (event) => {
  // 清空之前的图片数据
  pages.value = [];
  file = event.target.files[0];
  pdfName = file.name;
  startPage.value = 1;
  loading.value = true;
  if (file) {
    const typedarray = new Uint8Array(await file.arrayBuffer());
    pdfjsLib.GlobalWorkerOptions.workerSrc =
      "../../../node_modules/pdfjs-dist/build/pdf.worker.js";
    pdf = await pdfjsLib.getDocument(typedarray).promise;
   
    for (let i = 1; i <= pdf.numPages; i++) {
      const page = await pdf.getPage(i);
      const scale = quality.value;
      const viewport = page.getViewport({ scale });

      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d");
    
      canvas.height = parseInt(viewport.height);
      canvas.width = parseInt(viewport.width);

      //拍照当前的快照
      await page.render({ canvasContext: context, viewport: viewport }).promise;

      //生成图片,存入总pdf耶main
      const imgData = canvas.toDataURL("image/jpeg");
      pages.value.push(imgData);
    }
    endPage.value = pdf.numPages;
  }
  loading.value = false;
};

注意:这边的pdfjs-dist依赖包引入很坑人,还要自己指定workerSrc,我这个方法其实不算简单可以自行优化。

2.pdf展示

上文已经生成了相关的pdf图片数组,接下里是效果展示部分

vue 复制代码
	 <div id="pdfContainer" v-loading="loading">
      <div class="pdfPage" v-for="(page, index) in pages" :key="index">
        <div v-if="isTile">
          <div
            v-for="positionClass in positions"
            :key="positionClass"
            :class="`circle ${positionClass}`"
          ></div>
        </div>
        <div v-else>
          <div :class="chargePage(index + 1) ? optCellPositionClass : ''"></div>
        </div>
        <img :src="page" id="test" />
      </div>
    </div>

样式

css 复制代码
/* pdf预览样式 */
#pdfContainer {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-around;
}

.pdfPage {
  width: 180px;
  margin: 3px 3px;
  position: relative;
}

3.添加水印内容

水印分为图片水印和文字水印,可以做一个tab进行选择。这立面有一些他们自己的专属条件要求。formatTooltip也只是一个普通的拼接字符函数。

vue 复制代码
   <el-tabs tab-position="top" class="demo-tabs" v-model="tabs">
        <el-tab-pane label="文字水印">
          <div class="textContent">
            <span class="textTitle">水印文字:</span>
            <el-input
              v-model="content"
              :rows="2"
              type="textarea"
              style="width: 200px; margin-right: 80px"
              size="large"
              placeholder="请输入文字水印的内容"
            />
            <div>
              颜色与透明度
              <el-color-picker
                v-model="color"
                show-alpha
                :predefine="predefineColors"
                style="margin-left: 30px"
              />
              <div class="slider-demo-block">
                <div class="sliderTitle">大小</div>
                <el-slider
                  v-model="fontSize"
                  :min="0"
                  :max="100"
                  :format-tooltip="(value) => formatTooltip(value, 'px')"
                />
                {{ fontSize }}px
              </div>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="图片水印">
          <div class="textContent">
            <span class="textTitle">水印图片:</span>
            <input
              type="file"
              id="watermarkPicture"
              style="display: none"
              accept="image/*"
              @change="uploadPicture"
            />
            <el-button type="primary" style="margin-right: 160px">
              <label for="watermarkPicture">上传水印图片</label>
            </el-button>
            <div class="slider-demo-block">
              <div class="sliderTitle">缩放</div>
              <el-slider
                v-model="scaling"
                :format-tooltip="(value) => formatTooltip(value, '%')"
              />
              {{ scaling }}%
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>

4.水印公共属性设置

包括位置,水印布置方式(单个还是平铺多个),旋转,边距(这边的样式不提供,可以自己调一下简单样式)

vue 复制代码
<div class="textContent">
      <span class="textTitle">水印位置:</span>
      <div class="positionBox">
        <div
          v-for="index in 9"
          :key="index"
          class="cell"
          @click="optCell(index)"
        >
          <div :class="optCellPosition == index ? 'optCellStyle' : ''"></div>
        </div>
      </div>

      <div>
        <span class="sliderTitle">平铺 </span>
        <el-switch
          v-model="isTile"
          style="--el-switch-on-color: #13ce66; margin-right: 90px"
        />
      </div>

      <div>
        <div class="slider-demo-block">
          <div class="sliderTitle">边距</div>
          <el-slider
            v-model="margin"
            :format-tooltip="(value) => formatTooltip(value, 'px')"
            :min="0"
            :max="200"
          />
          <span>{{ margin }}px</span>
        </div>

        <div class="slider-demo-block">
          <div class="sliderTitle">旋转</div>
          <el-slider
            v-model="angle"
            :format-tooltip="(value) => formatTooltip(value, '°')"
            :min="-180"
            :max="180"
          />
          <span>{{ angle }}°</span>
        </div>
      </div>
    </div>

5.合成pdf,并下载

javascript 复制代码
/** 上传水印图片 */
const watermarkPicture = ref(null);
const pdfPicture = ref(null);
const uploadPicture = (event) => {
  watermarkPicture.value = event.target.files[0];

  if (watermarkPicture.value) {
    proxy.$modal.msgSuccess("上传成功!");
    const reader = new FileReader();
    reader.onload = (e) => {
      pdfPicture.value = e.target.result;
    };
    reader.readAsDataURL(watermarkPicture.value);
  } else {
    proxy.$modal.msgError("上传失败");
  }
};

/** 生成并下载pdf */
const generatePDF = async () => {
  console.log(oldQuality.value, " + ", quality.value);
  disabled.value = true;
  loading.value = true;
  const pdfDoc = new jsPDF({ unit: "px" });
  if (oldQuality.value != quality.value) {
    await getPage(); // 确保捕获getPage中可能出现的错误
  }

  for (let pageNum = 0; pageNum < pages.value.length; pageNum++) {
    const page = pages.value[pageNum];
    const img = new Image(page);
    img.src = page;
    // console.log(page);

    await new Promise((resolve, reject) => {
      img.onload = resolve;
      img.onerror = reject;
    });
    const imgWidth = img.naturalWidth;
    const imgHeight = img.naturalHeight;
    // console.log("size2", imgWidth, imgHeight);
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    canvas.width = imgWidth;
    canvas.height = imgHeight;

    let orientation = imgWidth > imgHeight ? "l" : "p";
    ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
    let i = optCellPosition.value;
    let length = 1;

    //判定是否平铺
    if (isTile.value == true) {
      i = 1;
      length = 9;
    } else {
      length = i;
    }

    for (i; i <= length; i++) {
      if (startPage.value <= pageNum + 1 && endPage.value >= pageNum + 1) {
        if (tabs.value == "1") {
          const watermarkImg = new Image();
          watermarkImg.src = pdfPicture.value;
          await new Promise((resolve) => {
            watermarkImg.onload = () => {
              ctx.save();
              let scaleWidth = (watermarkImg.width * scaling.value) / 100;
              let scaleHeight = (watermarkImg.height * scaling.value) / 100;
              let numberX = i % 3;
              let imgX = getPosition(numberX - 1, scaleWidth, imgWidth);
              let numberY = (i - 1 - ((i - 1) % 3)) / 3;
              let imgY = getPosition(numberY, scaleHeight, imgHeight);
              const rotate = (angle.value * Math.PI) / 180;
              ctx.translate(imgX, imgY);
              ctx.rotate(rotate);
              ctx.translate(-imgX, -imgY);

              // 水印缩放

              ctx.drawImage(watermarkImg, imgX, imgY, scaleWidth, scaleHeight);
              ctx.restore();
              resolve();
            };
          });
        } else {
          //添加文字水印
          const textLines = content.value.split("\n");
          ctx.textAlign = "center";
          ctx.font = `${fontSize.value}px Arial`;
          ctx.fillStyle = color.value;

          const lineHeight = fontSize.value;
          const textWidth = textLines.reduce((maxWidth, line) => {
            const metrics = ctx.measureText(line);
            return Math.max(maxWidth, metrics.width);
          }, 0);

          let numberX = i % 3;
          const x = getPositionX(numberX, textWidth, imgWidth);
          let numberY = (i - 1 - ((i - 1) % 3)) / 3;
          const y = getPosition(numberY, lineHeight, imgHeight);

          ctx.save();
          ctx.translate(x, y);
          let rotate = (angle.value * Math.PI) / 180;
          ctx.rotate(rotate); // 旋转水印
          textLines.forEach((line, index) => {
            ctx.fillText(line, 0, index * lineHeight);
          });
          ctx.restore();
        }
      }
    }

    let imageData = canvas.toDataURL("image/jpeg");
    pdfDoc.addPage(
      [imgWidth / quality.value, imgHeight / quality.value],
      orientation.toUpperCase()
    );
    pdfDoc.addImage(
      imageData,
      "JPEG",
      0,
      0,
      imgWidth / quality.value,
      imgHeight / quality.value
    );
  }
  if (pdfDoc.internal.pages.length > 0) {
    pdfDoc.deletePage(1);
  }
  pdfDoc.save(`${pdfName}.pdf`);
  loading.value = false;
  disabled.value = false;
};

/** number为地址参数*/
const getPositionX = (number, contentLength, imgWidth) => {
  let x = 0;
  if (number == 1) {
    x = margin.value + contentLength / 2;
  } else if (number == 2) {
    x = imgWidth / 2;
  } else {
    x = imgWidth - contentLength / 2 - margin.value;
  }
  return x;
};

const getPosition = (number, lineHeight, imgHeight) => {
  let y = 0;
  if (number == 0) {
    y = margin.value + lineHeight;
  } else if (number == 1) {
    y = (imgHeight - margin.value * 2) / 2 - lineHeight / 2 + margin.value;
  } else {
    y = imgHeight - margin.value - lineHeight;
  }
  return y;
};
相关推荐
brrdg_sefg2 小时前
Rust 在前端基建中的使用
前端·rust·状态模式
小奥超人8 小时前
PDF无法打印!怎么办?
windows·经验分享·pdf·办公技巧·pdf加密解密
m0_748241231 天前
ElasticPDF-新国产 PDF 编辑器开发框架(基于 pdf.js Web PDF批注开发,实现高亮多边形橡皮擦历史记录保存注释文字)
前端·pdf·编辑器
ComPDFKit1 天前
开源 JS PDF 库比较
pdf
杨浦老苏1 天前
开源PDF翻译工具PDFMathTranslate
人工智能·docker·ai·pdf·群晖·翻译
LostSpeed1 天前
在福昕(pdf)阅读器中导航到上次阅读页面的方法
pdf
旭久1 天前
SpringBoot的Thymeleaf做一个可自定义合并td的pdf表格
pdf·html·springboot
m0_748235242 天前
前端实现获取后端返回的文件流并下载
前端·状态模式
委婉待续2 天前
java抽奖系统(八)
java·开发语言·状态模式
神色自若2 天前
Net9为PDF文字替换,使用Spire.PDF版本10.12.4.1360
pdf