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;

相关推荐
拉不动的猪14 分钟前
无缝适配 PC 和移动端‌我们要注意哪些点呢
前端·javascript·面试
爱看书的小沐1 小时前
【小沐杂货铺】基于Three.JS绘制卫星轨迹Satellite(GIS 、WebGL、vue、react,提供全部源代码)
javascript·vue.js·webgl·three.js·卫星轨道·地球earth·satellite
王富贵的记录2 小时前
React 函数组件和类组件的区别
前端·javascript·react.js
巴巴_羊2 小时前
React Article模块
javascript·react.js·ecmascript
z_mazin2 小时前
正则表达式在爬虫中的应用:匹配 HTML 和 JSON 的技巧
javascript·爬虫·正则表达式
DevUI团队2 小时前
Electron 入门学习指南:快速搭建跨平台桌面应用
前端·javascript·electron
柳鲲鹏2 小时前
VUE3多国语言切换(国际化)
前端·javascript·vue.js
Cutey9163 小时前
前端SEO优化方案
前端·javascript
八了个戒4 小时前
「数据可视化 D3系列」入门第六章:比例尺的使用
前端·javascript·信息可视化·数据可视化·canvas