当前位置: 首页 > article >正文

pdfjs库使用记录1

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">PDF文件加载中...</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;

相关文章:

pdfjs库使用记录1

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 […...

Qt 创建QWidget的界面库(DLL)

【1】新建一个qt库项目 【2】在项目目录图标上右击&#xff0c;选择Add New... 【3】选择模版&#xff1a;Qt->Qt设计师界面类&#xff0c;选择Widget&#xff0c;填写界面类的名称、.h .cpp .ui名称 【4】创建C调用接口&#xff08;默认是创建C调用接口&#xff09; #ifnd…...

Django REST framework 并结合 `mixin` 的示例

下面为你提供一个使用 Django REST framework 并结合 mixin 的示例,该示例将实现一个简单的图书管理 API。 项目需求 我们要创建一个图书管理系统的 API,支持对图书信息的创建、读取、更新和删除操作。 实现步骤 1. 项目初始化 首先,确保你已经安装了 Django 和 Django…...

linux查看及修改用户过期时间

修改用户有效期 密码到期时间 sudo chage -E 2025-12-31 username sudo chage -M 180 username sudo chage -d $(date %F) username 查询用户密码到期时间 for user in $(cat /etc/passwd |cut -d: -f1); do echo $user; chage -l $user | grep "Password expires"; …...

Vue.directive自定义v-指令

翻阅文章有感&#xff0c;记录学习 vue前端菜单权限控制_vue权限管理菜单思路-CSDN博客 一、定义&#xff1a;Vue.directive是Vue框架中给开发者用于注册自定义指令和返回已注册指令的API 二、基本语法&#xff1a; // 注册 Vue.directive(my-directive, {bind: function () …...

AI Agent 元年,于 2025 开启

私人博客传送门 AI Agent 元年&#xff0c;于 2025 开启 | 魔筝炼药师...

Django 自带开发服务器

$ python manage.py runserver $ python manage.py runserver 666 # 用 666 端口 $ python manage.py runserver 0.0.0.0:8000 # 让局域网内其他客户端也可访问 $ python manage.py runserver --skip-checks # 跳过检查自动检查 $ python manage.py runserver --…...

Spring 数据库编程

Spring JDBC 传统的JDBC在操作数据库时&#xff0c;需要先打开数据库连接&#xff0c;执行SQL语句&#xff0c;然后封装结果&#xff0c;最后关闭数据库连接等资源。频繁的数据库操作会产生大量的重复代码&#xff0c;造成代码冗余&#xff0c;Spring的JDBC模块负责数据库资源…...

进阶篇|CAN FD 与性能优化

引言 1. CAN vs. CAN FD 对比 2. CAN FD 帧结构详解...

CTF--各种绕过哟

一、原网页&#xff1a; 二、步骤&#xff1a; 1.源代码&#xff1a; <?php highlight_file(flag.php); $_GET[id] urldecode($_GET[id]); $flag flag{xxxxxxxxxxxxxxxxxx}; if (isset($_GET[uname]) and isset($_POST[passwd])) {if ($_GET[uname] $_POST[passwd])pr…...

【Pandas】pandas DataFrame where

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行DataFrame.at快速访问和修改 DataFrame 中单个值的方法DataFrame.iat快速访问和修改 DataFrame 中单个值的方法DataFrame.loc用于基于标签&#xff08;行标签和列标签&#…...

嵌入式ARM RISCV toolchain工具 梳理arm-none-eabi-gcc

嵌入式TOOLchain工具 梳理 简介 本文总结和梳理一下一些toolchain的规则和原理&#xff0c;方便后续跨平台的时候&#xff0c;给大家使用toolchain做一个参考。 解释如何理解arm-none-eabi-gcc等含义&#xff0c;以及如何一看就知道该用什么编译器。 当然如果有哪里写的不是…...

OpenBMC:BmcWeb log输出

BmcWeb的log函数定义于:http\logging.hpp 说实话,个人觉得这一版的log函数有点炫技,使用起来也没有之前的版本方便,不过也还是值的参考一下。 1.如何输出log BMCWEB_LOG_ERROR("GetAll on path {} iface {} service {} failed with code {}",objectPath, inte…...

复现SCI图像增强(Toward fast, flexible, and robust low-light image enhancement.)

运行train.py报错 > File "/home/uriky/桌面/SCI-main/SCI-main/train.py", line 105, in main > train_queue torch.utils.data.DataLoader( File "/home/uriky/anaconda3/envs/AA/lib/python3.8/site-packages/torch/utils/data/dataloader.py&q…...

深入理解C++中string的深浅拷贝

目录 一、引言 二、浅拷贝与深拷贝的基本概念 2.1 浅拷贝 2.2 深拷贝 在C 中&#xff0c; string 类的深浅拷贝有着重要的区别。 浅拷贝 深拷贝 string 类中的其他构造函数及操作 resize 构造 构造&#xff08;赋值构造&#xff09; 构造&#xff08;拼接构造&#xf…...

性能测试面试题的详细解答

以下是性能测试面试题的详细解答&#xff1a; 1. 性能测试的流程是怎样的&#xff1f; 性能测试流程通常包括以下几个步骤&#xff1a; - **需求分析**&#xff1a;明确测试目标、性能指标&#xff08;如响应时间、吞吐量等&#xff09;。 - **环境搭建**&#xff1a;搭建测试环…...

第八篇:系统分析师第三遍——3、4章

目录 一、目标二、计划三、完成情况四、意外之喜(最少2点)1.计划内的明确认知和思想的提升标志2.计划外的具体事情提升内容和标志 五、总结 一、目标 通过参加考试&#xff0c;训练学习能力&#xff0c;而非单纯以拿证为目的。 1.在复习过程中&#xff0c;训练快速阅读能力、掌…...

Unity粒子特效打包后不显示

1.粒子发mesh&#xff0c;如果打包后不显示&#xff0c;尝试勾选r/w 2.如果还不行&#xff0c;mesh重做&#xff0c;目前发现ab包打出的&#xff0c;有的mesh会出问题&#xff0c;暂时原因不详。...

PFC 是什么?

现在进行液晶电视机和等离子电视机电路分析时、故障维修时&#xff0c;都经常的提到“PFC 电路”一词&#xff0c;这 在早期的电视机中是没有的&#xff0c;早期维修电视机的师傅从来没有接触过的&#xff0c;但是 PFC 电路是目前液晶电视机 和等离子电视机中不可缺少的电路。那…...

6.5 GitHub监控系统实战:双通道采集+动态调度打造高效运维体系

GitHub Sentinel Agent 定期更新功能设计与实现 关键词:GitHub API 集成、定时任务调度、Python 爬虫开发、SMTP 邮件通知、系统稳定性保障 1. GitHub 项目数据获取功能 1.1 双通道数据采集架构设计 #mermaid-svg-ZHJIMXcMAyDHVhmV {font-family:"trebuchet ms",v…...

楼梯上下检测数据集VOC+YOLO格式5462张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;5462 标注数量(xml文件个数)&#xff1a;5462 标注数量(txt文件个数)&#xff1a;5462 …...

消防营区管控:从智能仓储、装备管理、应急物资调用等多维度出发

近期&#xff0c;一系列消防安全热点事件引发了社会各界的广泛关注。某老旧城区的一场火灾&#xff0c;由于消防通道被杂物堵塞&#xff0c;消防车辆无法及时靠近火源&#xff0c;加之周边消防设施老化&#xff0c;灭火物资储备不足&#xff0c;导致火势迅速蔓延&#xff0c;造…...

Flutter 自定义插件基础

1、Flutter插件是什么&#xff1f;官方插件库 在开发Flutter应用过程中会涉及到平台相关接口调用&#xff0c;例如数据库操作、相机调用、外部浏览器跳转等业务场景。其实Flutter自身并不支持直接在平台上实现这些功能&#xff0c;而是通过插件包接口去调用指定平台API从而实现…...

解锁古籍中的气候密码,探索GPT/BERT在历史灾害研究中的前沿应用;气候史 文本挖掘 防灾减灾;台风案例、干旱案例、暴雨案例

历史灾害文献分析方法论的研究&#xff0c;是连接过去与未来的关键桥梁。通过对古籍、方志、档案等非结构化文本的系统性挖掘与量化分析&#xff0c;不仅能够重建千年尺度的灾害事件序列&#xff08;如台风、洪旱等&#xff09;&#xff0c;弥补仪器观测数据的时空局限性&#…...

【java 13天进阶Day12】XML和Dom4j,装饰模式,工厂模式,commons-io工具包,Base64

XML XML 指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09;&#xff0c;由各种标记(元素标签)组成。 可扩展&#xff1a;所有的标签都是自定义的&#xff0c;可以随意扩展的&#xff0c;如 。 XML 是一种标记语言&#xff0c;很类似 HTML&#xff0c;HTML…...

vue3 Element-plus修改内置样式复现代码

笔者在修改Element-plus的内置样式时&#xff0c;遇到一点挫折&#xff0c;现提供需求场景与解决方案。 一、实现&#xff08;1&#xff09;透明弹窗可拖拽&#xff0c;且不影响点击弹窗外内容&#xff1b;&#xff08;2&#xff09;弹窗内置表格&#xff0c;表格需修改样式颜色…...

工作督导 | 具有边缘型人格障碍倾向的高危来访者,咨询师如何应对?

一个学校心理中心&#xff0c;可能同时有几十位乃至数百位同学在接受咨询&#xff0c;其中大约10-20%是重点难点个案&#xff0c;一次督导如果只能督导1-2个个案&#xff0c;不足以保障所有危重难个案的有如何处理恰当、方向正确、快速解决、高效工作&#xff0c;是学校心理咨询…...

一本通 2063:【例1.4】牛吃牧草 1005:地球人口承载力估计

Topic&#xff1a; Ideas&#xff1a; 为什么把这两道题放在一起呢&#xff1f;就是因为这两道题很类似&#xff0c;都是很简单的数学题&#xff0c;只要你会列出数学等式&#xff0c;你就学会这道题了&#xff01; 下面把计算过程展示给大家 Code&#xff1a; //2025/04/18…...

图+文+语音一体化:多模态合成数据集构建的实战与方法论

目录 图文语音一体化&#xff1a;多模态合成数据集构建的实战与方法论 一、多模态合成数据的核心价值 二、系统架构概览 三、核心模块与实现建议 ✅ 1. 文→图&#xff1a;图像合成&#xff08;Text-to-Image&#xff09; ✅ 2. 图→文&#xff1a;自动描述&#xff08;I…...

c++:c++中的输入输出(二)

1.getline getline是包含于头文件&#xff1a;<string>的函数 作用&#xff1a;读取一行字符串&#xff08;包含空格&#xff09; 使用格式&#xff1a;getline(cin,str); string a;getline(cin, a); 假设我们有一个场景是需要识别一行字符串中的字母a的个数&#xff0c;…...