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;
};
相关推荐
一个处女座的程序猿21 小时前
LLMs之PDF:zeroX(一款PDF到Markdown 的视觉模型转换工具)的简介、安装和使用方法、案例应用之详细攻略
pdf·markdown·zerox
Dxy123931021621 小时前
python下载pdf
数据库·python·pdf
周亚鑫21 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
一名技术极客1 天前
Vue2 doc、excel、pdf、ppt、txt、图片以及视频等在线预览
pdf·powerpoint·excel·文件在线预览
wrx繁星点点1 天前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
JungleCoding1 天前
403 Request Entity Too Lager(请求体太大啦)
状态模式
S. Dylan2 天前
Edge浏览器打开PDF无法显示电子签章
edge·pdf
一马平川的大草原2 天前
如何基于pdf2image实现pdf批量转换为图片
计算机视觉·pdf·文件拆分
m0_594526302 天前
Python批量合并多个PDF
java·python·pdf
hairenjing11232 天前
将图片添加到 PDF 的 5 种方法
pdf