Day 28 - 用 canvas 与 pdfjs 做文件签名(上)

前述

今天用前面做过的小画家相似功能,来完成一个可以在文件上面签名的功能~
当然也会有新的东西可以玩。

思路

我们将文件签名分成三个步骤

 <div className="App">
  <h1>步骤一</h1>
  <h3>绘制签名档</h3>
  <SignBox></SignBox>
  <h1>步骤二</h1>
  <h3>上传文件</h3>
  <UploadFile></UploadFile>
  <h1>步骤三</h1>
  <h3>合并输出</h3>
  <Output></Output>
</div>

今天先教学 绘制签名档上传文件 的部分

绘制签名档

根据以前的操作,相信已经对 mouse 的事件滚瓜烂熟了,
这里就不赘述罗。

code

import React, { useEffect, useRef, useState } from "react";
import getTouchPos from "../../utils/getTouchPos";
import getMousePos from "../../utils/getMousePos";

import { useAtom } from "jotai";
import { signAtom } from "../../data";

const canvasSize = 500;

const SignFile = () => {
  const canvasRef = useRef(null);
  const [canvas, setCanvas] = useState(null);
  const [ctx, setCtx] = useState(null);
  const [src, setSrc] = useState(null);

  const [drawing, setDrawing] = useState(false);

  const [_, setSignData] = useAtom(signAtom);

  useEffect(() => {
    const c = canvasRef.current;
    setCanvas(c);
    if (c) setCtx(c.getContext("2d"));
  }, [canvasRef]);

  /** 开始 */
  const handleTouchStart = (event) => {
    setDrawing(true);
    const touchPos = getTouchPos(canvas, event);
    ctx.beginPath(touchPos.x, touchPos.y);
    ctx.moveTo(touchPos.x, touchPos.y);
    event.preventDefault();
  };

  const handleMouseDown = (event) => {
    setDrawing(true);
    const mousePos = getMousePos(canvas, event);
    ctx.beginPath();
    ctx.moveTo(mousePos.x, mousePos.y);
    event.preventDefault();
  };

  /** 移动 */
  const handleTouchMove = (event) => {
    if (!drawing) return;
    const touchPos = getTouchPos(canvas, event);
    ctx.lineWidth = 2;
    ctx.lineCap = "round"; // 绘制圆形的结束线帽
    ctx.lineJoin = "round"; // 两条线条交汇时,建立圆形边角
    ctx.shadowBlur = 1; // 边缘模糊,防止直线边缘出现锯齿
    ctx.shadowColor = "black"; // 边缘颜色
    ctx.lineTo(touchPos.x, touchPos.y);
    ctx.stroke();
  };

  const handleMouseMove = (event) => {
    if (!drawing) return;
    const mousePos = getMousePos(canvas, event);
    ctx.lineWidth = 2;
    ctx.lineCap = "round"; // 绘制圆形的结束线帽
    ctx.lineJoin = "round"; // 两条线条交汇时,建立圆形边角
    ctx.shadowBlur = 1; // 边缘模糊,防止直线边缘出现锯齿
    ctx.shadowColor = "black"; // 边缘颜色
    ctx.lineTo(mousePos.x, mousePos.y);
    ctx.stroke();
  };

  /** 结束 */
  const handleTouchEnd = (event) => {
    setDrawing(false);
  };

  const handleMouseUp = (event) => {
    setDrawing(false);
  };

  /** 清除 */
  const handleClear = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  };

  /** 转图片 */
  const handleConvertToImage = () => {
    const image = canvas.toDataURL();
    setSignData(image);
    setSrc(image);
  };

  return (
    <div>
      <canvas
        style={{ background: "#EEE" }}
        ref={canvasRef}
        width={canvasSize}
        height={canvasSize}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
      ></canvas>
      <div>
        <button onClick={handleClear}>清除</button>
        <button onClick={handleConvertToImage}>转图</button>
      </div>
      {src && (
        <img
          src={src}
          alt="signImage"
          style={{ color: "#FFF", border: "none" }}
        />
      )}
    </div>
  );
};

export default SignFile;

补充一下,这次跨组件都用 jotai ,想要用 recoil 之类的都是可行的。

迅速的就完成画签名啦,而且同时支援滑鼠触控事件。

检视

上传档案

做文件签名当然也需要文件的支持啦,这段是本篇章的重点!

我们需要学习新的lib pdfjs ,迅速地让他帮我们做到 pdf to canvas

pdfjs 文档

react-pdf npm

至於怎麽使用,就请大家去翻翻文档吧

import React, { useEffect, useRef, useState } from "react";
import getScaledDim from "../../utils/getScaledDim";
import { useAtom } from "jotai";
import { bgFileAtom } from "../../data";

import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

const canvasSize = 500;

const UploadFile = () => {
  const canvasRef = useRef(null);
  const [canvas, setCanvas] = useState(null);
  const [ctx, setCtx] = useState(null);
  const [src, setSrc] = useState(null);

  const [bgFileData, setBgFileData] = useAtom(bgFileAtom);

  useEffect(() => {
    const c = canvasRef.current;
    setCanvas(c);
    if (c) setCtx(c.getContext("2d"));
  }, [canvasRef]);

  /** image */
  const handleUploadImage = (event) => {
    const f = event.target.files[0];
    const ctx = canvasRef.current.getContext("2d");
    const img = new Image();
    img.onload = function () {
      const scaled = getScaledDim(img, canvasSize, canvasSize);
      // scale canvas to image
      ctx.width = scaled.width;
      ctx.height = scaled.height;
      // draw image
      ctx.drawImage(img, 0, 0, ctx.width, ctx.height);
    };
    img.src = URL.createObjectURL(f);
  };

  /** pdf */
  const handleUploadPdf = (event) => {
    const file = event.target.files[0];
    if (file.type === "application/pdf") {
      let fileReader = new FileReader();
      fileReader.onload = function () {
        const pdfData = new Uint8Array(this.result);
        // Using DocumentInitParameters object to load binary data.
        const loadingTask = pdfjs.getDocument({ data: pdfData });
        loadingTask.promise.then(
          function (pdf) {
            console.log("PDF loaded");
            // Fetch the first page
            const pageNumber = 1;
            pdf.getPage(pageNumber).then(function (page) {
              console.log("Page loaded");

              const scale = 1.5;
              const viewport = page.getViewport({ scale: scale });

              // Prepare canvas using PDF page dimensions
              canvas.height = viewport.height;
              canvas.width = viewport.width;
              // Render PDF page into canvas context
              const renderContext = {
                canvasContext: ctx,
                viewport: viewport
              };
              const renderTask = page.render(renderContext);
              renderTask.promise.then(function () {
                console.log("Page rendered");
              });
            });
          },
          function (reason) {
            // PDF loading error
            console.error(reason);
          }
        );
      };
      fileReader.readAsArrayBuffer(file);
    }
  };

  const handleConvertToImage = () => {
    const image = canvas.toDataURL();
    setBgFileData(image);
    setSrc(image);
  };

  return (
    <div>
      <div style={{ marginBottom: `1rem` }}>
        上传 Image:
        <input type="file" onChange={handleUploadImage} />
      </div>
      <div>
        上传 PDF:
        <input accept=".pdf" type="file" onChange={handleUploadPdf} />
      </div>

      <canvas ref={canvasRef} width={canvasSize} height={canvasSize}></canvas>
      <button onClick={handleConvertToImage}>转图</button>
      <img src={src} alt="imagePdf" />
    </div>
  );
};

export default UploadFile;

// https://mozilla.github.io/pdf.js/examples/index.html#interactive-examples

检视

这样材料就算是准备好了,明天来准备合并吧!!

codesendbox


<<:  【第二十九天 - Python 反序列化】

>>:  30-28 之 DDD 战术篇1 - Entity 与 Value Object

DAY22 用 Azure Machine Learning SDK 建立环境

DAY22 用 Azure Machine Learning SDK 建立环境 我们在前面图形化介面...

[DAY 6] _stm32f103c8t6_暂存器查找方法

DAY 5提到暂存器如何查找,还有开启时钟才能对GPIO口操作,我补充一下昨天没贴到的暂存器地图,在...

Day29,使用Dex、OIDC为你的Kubernetes再上一道锁 (2/2)

正文 如果还没有看Day28的话,建议可以先回去看,不然接下来可能搞不清楚状况。 延续昨天的内容,我...

Day16 - [丰收款] 取得PayToken的最後一哩路很慢长

昨天抱病撰文,终於在本机端将单笔资料透过ORM的方法,成功将新增的订单资料更新到Heroku Pos...

D11 - 「数位×IN×OUT」:数位功能

数位 I/O 视窗当然是要有数位讯号相关功能啦。 数位 I/O 功能 在 Supported Mod...