pdfjs库使用记录1

打印 上一主题 下一主题

主题 1712|帖子 1712|积分 5136

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
import React, { useEffect, useState, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist';

// 设置 worker 路径
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js';

const PDFViewer = ({ url }) => {
  const [pdf, setPdf] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);
  const [numPages, setNumPages] = useState(0);
  const [pageRendering, setPageRendering] = useState(false);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const canvasRef = useRef(null);
  const linkLayerRef = useRef(null);  // 添加链接层的引用
  const pageCache = useRef(new Map());
  const scale = useRef(1);  // 添加scale引用以在差别函数间共享

  // 添加水印函数
  const addWatermark = (canvas, scale) => {
    const ctx = canvas.getContext('2d');
    const devicePixelRatio = window.devicePixelRatio || 1;

    // 生存当前上下文状态
    ctx.save();

    // 设置水印样式
    ctx.globalAlpha = 0.2; // 水印透明度
    ctx.fillStyle = '#000'; // 水印颜色

    // 盘算底子字体巨细(根据canvas宽度动态调整)
    const baseFontSize = Math.min(canvas.width, canvas.height) * 0.03; // 3% 的画布巨细
    const fontSize = baseFontSize * devicePixelRatio;
    ctx.font = `${fontSize}px Arial`;

    // 水印文本
    const text1 = '45380867';
    const text2 = 'Jun Xiao';

    // 盘算水印尺寸
    const text1Width = ctx.measureText(text1).width;
    const text2Width = ctx.measureText(text2).width;
    const lineHeight = fontSize * 1.2;
    const watermarkWidth = Math.max(text1Width, text2Width);
    const watermarkHeight = lineHeight * 2;

    // 盘算水印网格
    const xGap = watermarkWidth * 2.5; // 水印之间的横向间距
    const yGap = watermarkHeight * 2.5; // 水印之间的纵向间距

    // 旋转角度(25度)
    const angle = -25 * Math.PI / 180;

    // 绘制水印网格
    for (let y = -yGap; y < canvas.height + yGap; y += yGap) {
      for (let x = -xGap; x < canvas.width + xGap; x += xGap) {
        ctx.save();

        // 移动到水印位置并旋转
        ctx.translate(x, y);
        ctx.rotate(angle);

        // 绘制两行文本
        ctx.fillText(text1, -text1Width / 2, 0);
        ctx.fillText(text2, -text2Width / 2, lineHeight);

        ctx.restore();
      }
    }

    // 恢复上下文状态
    ctx.restore();
  };
  // 添加处理链接的函数
  const setupLinkLayer = (page, viewport) => {
    const linkLayer = linkLayerRef.current;
    if (!linkLayer) return;

    // 扫除旧的链接
    while (linkLayer.firstChild) {
      linkLayer.removeChild(linkLayer.firstChild);
    }

    // 获取页面的注解(包括链接)
    page.getAnnotations().then(annotations => {
      annotations.forEach(annotation => {
        if (annotation.subtype === 'Link' && annotation.url) {
          // 创建链接元素
          const link = document.createElement('a');
          const bounds = viewport.convertToViewportRectangle(annotation.rect);

          // 设置链接样式
          link.href = annotation.url;
          link.target = '_blank';  // 在新标签页中打开
          link.style.position = 'absolute';
          link.style.left = `${Math.min(bounds[0], bounds[2])}px`;
          link.style.top = `${Math.min(bounds[1], bounds[3])}px`;
          link.style.width = `${Math.abs(bounds[2] - bounds[0])}px`;
          link.style.height = `${Math.abs(bounds[3] - bounds[1])}px`;
          link.style.cursor = 'pointer';

          // 添加到链接层
          linkLayer.appendChild(link);
        }
      });
    });
  };
  // 初始化 PDF
  useEffect(() => {
    const loadPDF = async () => {
      if (!url) return;

      try {
        setLoading(true);
        setError(null);

        // 创建加载任务
        const loadingTask = pdfjsLib.getDocument(url);
        const pdfDoc = await loadingTask.promise;

        setPdf(pdfDoc);
        setNumPages(pdfDoc.numPages);
      } catch (error) {
        console.error('Error loading PDF:', error);
        setError('PDF加载失败,请稍后重试');
      } finally {
        setLoading(false);
      }
    };

    loadPDF();

    return () => {
      // 清算缓存的页面
      pageCache.current.clear();
      if (pdf) {
        pdf.destroy();
      }
    };
  }, [url]);

  // 渲染页面
  const renderPage = async (pageNum) => {
    if (pageRendering || !pdf) return;

    setPageRendering(true);

    try {
      // 检查缓存
      if (!pageCache.current.has(pageNum)) {
        const page = await pdf.getPage(pageNum);
        pageCache.current.set(pageNum, page);
      }

      const page = pageCache.current.get(pageNum);
      const canvas = canvasRef.current;
      const ctx = canvas.getContext('2d');

      // 盘算适合屏幕的缩放比例
      const viewport = page.getViewport({ scale: 1 });
      const devicePixelRatio = window.devicePixelRatio || 1;
      const containerWidth = canvas.parentElement.clientWidth;
      const scale = (containerWidth / viewport.width) * devicePixelRatio;

      // 设置canvas尺寸
      const scaledViewport = page.getViewport({ scale });
      canvas.width = scaledViewport.width;
      canvas.height = scaledViewport.height;
      canvas.style.width = `${containerWidth}px`;
      canvas.style.height = `${scaledViewport.height / devicePixelRatio}px`;

      // 设置链接层尺寸和位置
      if (linkLayerRef.current) {
        linkLayerRef.current.style.width = `${containerWidth}px`;
        linkLayerRef.current.style.height = `${scaledViewport.height / devicePixelRatio}px`;
      }

      // 渲染PDF页面
      const renderContext = {
        canvasContext: ctx,
        viewport: scaledViewport,
        enableWebGL: true,
      };

      await page.render(renderContext).promise;
      // 设置链接
      setupLinkLayer(page, scaledViewport);
      // 在PDF页面渲染完成后添加水印
      addWatermark(canvas, scale);

    } catch (error) {
      console.error('Error rendering page:', error);
      setError('页面渲染失败,请刷新重试');
    } finally {
      setPageRendering(false);
    }
  };

  // 页面变革时重新渲染
  useEffect(() => {
    renderPage(currentPage);
  }, [currentPage, pdf]);

  // 内存管理:清算不可见页面的缓存
  useEffect(() => {
    const cleanupCache = () => {
      if (pageCache.current.size > 3) { // 只生存当前页面附近的几页
        const pagesToKeep = new Set([
          currentPage,
          currentPage - 1,
          currentPage + 1
        ]);

        pageCache.current.forEach((page, pageNum) => {
          if (!pagesToKeep.has(pageNum)) {
            // 确保在删除缓存前释放页面资源
            page.cleanup();
            pageCache.current.delete(pageNum);
          }
        });
      }
    };

    cleanupCache();
  }, [currentPage]);

  // 处理窗口巨细变革
  useEffect(() => {
    const handleResize = () => {
      renderPage(currentPage);
    };

    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, [currentPage]);

  if (loading) {
    return (
      <div className="loading">
        <div className="loading-text">DF文件加载中...</div>
        <div className="loading-spinner"></div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="error">
        <div className="error-message">{error}</div>
        <button onClick={() => window.location.reload()} className="retry-button">
          重试
        </button>
      </div>
    );
  }

  return (
    <div className="pdf-viewer">
      <div className="pdf-controls">
        <button
          onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
          disabled={currentPage <= 1 || pageRendering}
          className="control-button"
        >
          上一页
        </button>
        <span className="page-info">{`第 ${currentPage} 页,共 ${numPages} 页`}</span>
        <button
          onClick={() => setCurrentPage(prev => Math.min(prev + 1, numPages))}
          disabled={currentPage >= numPages || pageRendering}
          className="control-button"
        >
          下一页
        </button>
      </div>
      <div className="pdf-container">
        <div className="canvas-container" style={{ position: 'relative' }}>
          <canvas ref={canvasRef} className="pdf-canvas" />
          <div
            ref={linkLayerRef}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              pointerEvents: 'none'  // 允许点击穿透到链接
            }}
            className="link-layer"
          />
        </div>
      </div>
    </div>
  );
};

export default PDFViewer;

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊落一身雪

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表