提交 04646f87 authored 作者: gzcnkilys_admin's avatar gzcnkilys_admin

fix: 全局引入PdfViewer 的CSS,否则,样式不对,文本层无法渲染,直接作为turn.js渲染页面,优化选中效果。

上级 b1ce5555
......@@ -9,6 +9,8 @@
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>PDF阅读器</title>
<!-- pdf相关 - 放在最后确保优先级 -->
<link rel="stylesheet" type="text/css" href="/pdfjs-dist/web/pdf_viewer.css" />
<script src="/pdfjs-dist/build/pdf.js"></script>
<script src="/pdfjs-dist/web/pdf_viewer.js"></script>
<!-- 引入jQuery -->
......
......@@ -15,7 +15,6 @@
"element-plus": "^2.9.8",
"lodash": "^4.17.21",
"modernizr": "^3.6.0",
"pdfjs-dist": "^5.4.54",
"turn.js": "^1.0.5",
"vant": "^4.9.19",
"vue": "^3.5.13",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -74,10 +74,10 @@
bottom: 0px;
z-index: -1;
cursor: default;
/* -webkit-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; */
user-select: none;
}
.textLayer .endOfContent.active {
......
......@@ -314,6 +314,8 @@
if (_pdfjsLib.AnnotationLayer) {
// this.div.innerHTML = "";
// debugger
var parameters = {
viewport: viewport.clone({ dontFlip: true }),
div: this.div,
......@@ -337,6 +339,7 @@
var annotations = this.pdfPage.getAnnotation();
// debugger
if (_this._cancelled) {
return;
}
......
......@@ -11,7 +11,6 @@ declare module 'vue' {
AnnotationInputDialog: typeof import('./components/PdfViewer/annotationInputDialog/index.vue')['default']
BookMarks: typeof import('./components/GuideMobile/bookMarks.vue')['default']
BookReader: typeof import('./components/BookReader/index.vue')['default']
copy: typeof import('./components/BookReader/index copy.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
......
......@@ -20,17 +20,10 @@
},
]"
>
<!-- PDF页面使用canvas -->
<canvas
v-if="page.isPdf"
:data-page-index="index"
:class="['page-canvas', { 'wide-canvas': page.isWide }]"
:ref="`canvas${index}`"
></canvas>
<!-- PDF页面由PdfViewer动态渲染,不需要静态元素 -->
<!-- 图片页面使用img -->
<img
v-else
v-if="!page.isPdf"
:data-page-index="index"
:src="page.src"
:alt="`第 ${page.page_num} 页`"
......@@ -169,19 +162,34 @@
></svg-icon>
</el-tooltip>
<!-- PDF清晰度开关 -->
<!-- PDF渲染模式切换 -->
<el-tooltip
v-if="props.isPdf"
:content="props.highQualityRendering ? '降低清晰度' : '提高清晰度'"
v-if="props.isPdf && textLayerEnabled"
:content="svgRendererEnabled ? '切换到Canvas模式' : '切换到SVG模式'"
placement="top"
effect="dark"
>
<svg-icon
:name="props.highQualityRendering ? 'zoom-in' : 'zoom-out'"
:name="svgRendererEnabled ? 'zoom-out' : 'zoom-in'"
:size="20"
@click="toggleHighQuality"
@click="toggleRenderer"
></svg-icon>
</el-tooltip>
<!-- 文本层调试开关 -->
<el-tooltip
v-if="props.isPdf && textLayerEnabled"
:content="textLayerVisible ? '隐藏文本层' : '显示文本层'"
placement="top"
effect="dark"
>
<svg-icon
:name="textLayerVisible ? 'hign' : 'note'"
:size="20"
@click="toggleTextLayerVisibility"
></svg-icon>
</el-tooltip>
<!-- <svg-icon name="download" size="20"></svg-icon> -->
<!-- <svg-icon name="fullscreen" size="20"></svg-icon> -->
</el-col>
......@@ -205,7 +213,63 @@ import VueEasyLightbox from "vue-easy-lightbox";
import GuideMobile from "../GuideMobile/index.vue";
import audioFlip from "@/assets/audio/flip.mp3";
import throttle from "lodash/throttle";
import * as pdfjsLib from "pdfjs-dist";
// PDF.js 相关变量声明
let pdfjsLib = null;
let pdfjsViewer = null;
// 动态加载PDF.js
const loadPdfJs = async () => {
try {
// 检查全局变量是否已经存在(通过script标签加载)
if (window.pdfjsLib && window.pdfjsViewer) {
pdfjsLib = window.pdfjsLib;
pdfjsViewer = window.pdfjsViewer;
console.log('PDF.js already loaded from script tags');
return;
}
// 如果全局变量不存在,则动态创建script标签加载
await loadScript('/pdfjs-dist/build/pdf.js');
await loadScript('/pdfjs-dist/web/pdf_viewer.js');
// 等待一小段时间确保脚本加载完成
await new Promise(resolve => setTimeout(resolve, 100));
// 从全局变量获取
if (window.pdfjsLib && window.pdfjsViewer) {
pdfjsLib = window.pdfjsLib;
pdfjsViewer = window.pdfjsViewer;
// 设置PDF.js的worker路径
// if (pdfjsLib.GlobalWorkerOptions) {
// pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-dist/web/pdf.worker.js';
// }
console.log('PDF.js loaded successfully via script tags');
} else {
throw new Error('PDF.js failed to load');
}
} catch (error) {
console.error('Failed to load PDF.js:', error);
}
};
// 动态加载script标签的辅助函数
const loadScript = (src) => {
return new Promise((resolve, reject) => {
// 检查是否已经加载过
if (document.querySelector(`script[src="${src}"]`)) {
resolve();
return;
}
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
};
const props = defineProps({
pages: {
type: Array,
......@@ -229,10 +293,11 @@ const props = defineProps({
type: Boolean,
default: true,
},
highQualityRendering: {
enableSvgRenderer: {
type: Boolean,
default: true,
default: false,
},
});
const magazine = ref(null);
......@@ -267,9 +332,22 @@ const totalPdfPages = ref(0);
const pdfLoading = ref(false);
const textLayerEnabled = ref(props.enableTextLayer); // 文字层开关状态
// const textLayerEnabled = ref(false); // 文字层开关状态
// SVG渲染器开关状态
const svgRendererEnabled = ref(
localStorage.getItem("bookReaderSvgRenderer") === "true" || props.enableSvgRenderer
);
// 文本层可见性状态(用于调试)
const textLayerVisible = ref(false);
const renderedPages = ref(new Set()); // 跟踪已渲染的页面
const pdfInitialized = ref(false); // 跟踪PDF是否已完成初始化渲染
// PdfViewer相关状态
const pdfViewerInstances = ref(new Map()); // 存储每个页面的PdfViewer实例
const eventBus = ref(null); // 事件总线
const pdfLinkService = ref(null); // PDF链接服务
// Add new refs for sound and directory
const pageTurnSound = ref(null);
const audioInitialized = ref(false);
......@@ -386,15 +464,15 @@ const loadImage = async (pageIndex) => {
const loadVisiblePages = async (currentPageNum) => {
if (!magazine.value) return;
const $magazine = $(magazine.value);
const totalPages = $magazine.turn("pages");
// 使用processedPages的长度而不是turn.js的pages值
const totalPages = processedPages.value.length;
// 加载当前页、上一页、下一页
const startPage = Math.max(1, currentPageNum - 1);
const endPage = Math.min(totalPages, currentPageNum + 1);
const highPriority = [currentPageNum, currentPageNum + 1]; // 优先加载当前和下一页
console.log(`预加载页面: ${currentPageNum}, 范围: ${startPage}-${endPage}`);
console.log(`预加载页面: ${currentPageNum}, 范围: ${startPage}-${endPage}, 总页数: ${totalPages}`);
try {
// 静默加载,不显示loading状态
......@@ -424,15 +502,15 @@ const renderVisiblePdfPages = async (currentPageNum) => {
return;
}
const $magazine = $(magazine.value);
const totalPages = $magazine.turn("pages");
// 使用processedPages的长度而不是turn.js的pages值,因为turn.js可能还没有完全初始化
const totalPages = processedPages.value.length;
// 渲染当前页、上一页、下一页
const startPage = Math.max(1, currentPageNum - 1);
const endPage = Math.min(totalPages, currentPageNum + 1);
const highPriority = [currentPageNum, currentPageNum + 1]; // 优先渲染当前和下一页
console.log(`渲染PDF页面: ${currentPageNum}, 范围: ${startPage}-${endPage}`);
console.log(`渲染PDF页面: ${currentPageNum}, 范围: ${startPage}-${endPage}, 总页数: ${totalPages}`);
try {
console.log(`开始渲染高优先级页面: ${highPriority.join(", ")}`);
......@@ -465,6 +543,39 @@ const initBook = async () => {
destroyTurn();
checkMobile();
// 确保DOM中有正确数量的页面元素
const $magazine = $(magazine.value);
const expectedPages = processedPages.value.length;
const currentPages = $magazine.children().length;
console.log(`初始化turn.js: 期望页面数=${expectedPages}, 当前DOM页面数=${currentPages}`);
// 如果DOM中的页面数量不正确,重新创建页面元素
if (currentPages !== expectedPages) {
console.log(`页面数量不匹配,重新创建页面元素`);
$magazine.empty();
// 为每个页面创建DOM元素
processedPages.value.forEach((page, index) => {
const pageDiv = document.createElement('div');
pageDiv.className = `page p${index + 1}`;
pageDiv.setAttribute('data-page-index', index);
pageDiv.setAttribute('data-page-num', page.page_num);
// 如果是PDF页面,添加特殊标识
if (page.isPdf) {
pageDiv.setAttribute('data-is-pdf', 'true');
}
$magazine.append(pageDiv);
});
console.log(`重新创建了 ${processedPages.value.length} 个页面元素`);
// 等待DOM更新完成
await nextTick();
}
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
......@@ -519,18 +630,10 @@ const initBook = async () => {
});
}
// 处理PDF页面的canvas
const $canvas = $page.find("canvas");
if ($canvas.length) {
$canvas.css({
width: "100%",
height: "100%",
display: "block",
});
}
// PDF页面由PdfViewer动态渲染,不需要处理静态canvas
// 确保PDF页面容器有正确的尺寸
if ($page.find("canvas").length) {
if ($page.find("img").length === 0) {
// 这是PDF页面,设置容器尺寸
$page.css({
width: pageWidth,
height: pageHeight,
......@@ -635,11 +738,22 @@ const loadPdf = async () => {
console.log("开始加载PDF:", props.pdfUrl);
// 加载PDF文档
if (!pdfjsLib) {
throw new Error('PDF.js not loaded');
}
const loadingTask = pdfjsLib.getDocument(props.pdfUrl);
const rawPdfDocument = await loadingTask.promise;
pdfDocument.value = rawPdfDocument;
totalPdfPages.value = rawPdfDocument.numPages;
console.log('pdfjsViewer exists:', !!pdfjsViewer);
console.log('DefaultTextLayerFactory exists:', !!pdfjsViewer.DefaultTextLayerFactory);
// 设置PDF链接服务
if (pdfLinkService.value) {
pdfLinkService.value.setDocument(rawPdfDocument);
}
console.log("PDF加载成功,总页数:", totalPdfPages.value);
// 处理PDF页面
......@@ -647,8 +761,12 @@ const loadPdf = async () => {
// 清除之前的渲染状态
renderedPages.value.clear();
pdfViewerInstances.value.clear(); // 清除之前的页面视图实例
pdfInitialized.value = false; // 重置PDF初始化状态
// 等待Vue响应式更新完成
await nextTick();
// 初始化turn.js
await initBook();
......@@ -713,7 +831,7 @@ const destroyTurn = () => {
// 初始化 Intersection Observer
// 渲染单个PDF页面
// 渲染单个PDF页面 - 使用PdfViewer的PDFPageView
const renderPdfPage = async (pageIndex) => {
if (!pdfDocument.value || !magazine.value) return;
......@@ -725,13 +843,9 @@ const renderPdfPage = async (pageIndex) => {
return;
}
console.log(`开始渲染PDF页面 ${pageNum}`);
console.log(`开始渲染PDF页面 ${pageNum},使用PdfViewer`);
try {
// 使用 toRaw 将 Vue3 响应式对象转换为原始对象,避免 Proxy 兼容性问题
const rawPdfDocument = toRaw(pdfDocument.value);
const page = await rawPdfDocument.getPage(pageNum);
// 获取页面容器
const pageContainer = magazine.value.querySelector(`.p${pageNum}`);
if (!pageContainer) {
......@@ -739,405 +853,225 @@ const renderPdfPage = async (pageIndex) => {
return;
}
// 检查是否已经渲染过
let canvas = pageContainer.querySelector("canvas");
let isNewCanvas = false;
// 清理容器内容
pageContainer.innerHTML = '';
// 如果canvas已经渲染过,创建新的canvas
if (canvas && canvas.dataset.rendered === "true") {
const newCanvas = document.createElement("canvas");
newCanvas.className = canvas.className;
newCanvas.style.cssText = canvas.style.cssText;
pageContainer.replaceChild(newCanvas, canvas);
canvas = newCanvas;
isNewCanvas = true;
}
// 创建页面div元素
const pageDiv = document.createElement('div');
pageDiv.className = 'pdf-page-container';
pageDiv.setAttribute('data-page-number', pageNum);
pageDiv.style.position = 'relative';
pageDiv.style.width = '100%';
pageDiv.style.height = '100%';
pageDiv.style.overflow = 'hidden';
pageDiv.style.display = 'flex';
pageDiv.style.alignItems = 'center';
pageDiv.style.justifyContent = 'center';
if (!canvas) {
console.warn(`Canvas not found for page ${pageNum}`);
return;
}
// 将页面div添加到容器中
pageContainer.appendChild(pageDiv);
// 计算缩放比例(考虑设备像素比,提高清晰度)
const viewport = page.getViewport({ scale: 1.0 });
// 等待DOM更新完成
await nextTick();
// 获取容器尺寸,如果容器尺寸为0,则使用turn.js设置的页面尺寸
let containerWidth = canvas.clientWidth || pageContainer.clientWidth;
let containerHeight = canvas.clientHeight || pageContainer.clientHeight;
// 获取容器实际尺寸 - 使用更可靠的方法
let containerWidth = pageContainer.clientWidth;
let containerHeight = pageContainer.clientHeight;
// 如果容器尺寸为0,尝试从turn.js获取页面尺寸
// 如果容器尺寸为0,等待turn.js完全初始化
if (containerWidth === 0 || containerHeight === 0) {
const $magazine = $(magazine.value);
if ($magazine.length && $magazine.turn) {
const turnSize = $magazine.turn("size");
if (turnSize && turnSize.width && turnSize.height) {
// 移动端使用单页尺寸,桌面端使用双页尺寸的一半
if (isMobile.value) {
containerWidth = turnSize.width;
containerHeight = turnSize.height;
} else {
containerWidth = turnSize.width / 2;
containerHeight = turnSize.height;
}
}
}
}
// 如果仍然无法获取尺寸,使用默认尺寸
if (containerWidth === 0 || containerHeight === 0) {
console.warn(`页面${pageNum}容器尺寸为0,使用默认尺寸`);
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
if (isMobile.value) {
containerWidth = viewportWidth - 20;
containerHeight = (containerWidth * 4) / 3;
if (containerHeight > viewportHeight - 100) {
containerHeight = viewportHeight - 92;
containerWidth = (containerHeight * 3) / 4;
}
console.log(`页面${pageNum}容器尺寸为0,等待turn.js初始化...`);
// 等待turn.js完成页面布局
await new Promise((resolve) => {
const checkSize = () => {
const width = pageContainer.clientWidth;
const height = pageContainer.clientHeight;
if (width > 0 && height > 0) {
containerWidth = width;
containerHeight = height;
resolve();
} else {
containerWidth = Math.min(600, viewportWidth / 2 - 50);
containerHeight = Math.min(800, viewportHeight - 100);
setTimeout(checkSize, 50);
}
};
checkSize();
});
}
console.log(`页面${pageNum}容器尺寸: ${containerWidth}x${containerHeight}`);
// 获取设备像素比,提高高分辨率屏幕的显示质量
const devicePixelRatio = window.devicePixelRatio || 1;
const scaleX = containerWidth / viewport.width;
const scaleY = containerHeight / viewport.height;
const baseScale = Math.min(scaleX, scaleY);
// 根据清晰度设置决定是否使用高分辨率
const scale = props.highQualityRendering
? baseScale * devicePixelRatio
: baseScale;
// 设置canvas尺寸(使用高分辨率)
const scaledViewport = page.getViewport({ scale });
canvas.width = Math.floor(scaledViewport.width);
canvas.height = Math.floor(scaledViewport.height);
// 设置页面div的尺寸
pageDiv.style.width = `${containerWidth}px`;
pageDiv.style.height = `${containerHeight}px`;
// 设置CSS尺寸(保持显示大小不变)
canvas.style.width = `${containerWidth}px`;
canvas.style.height = `${containerHeight}px`;
console.log(
`页面${pageIndex + 1}缩放信息: 基础缩放=${baseScale.toFixed(
3
)}, 设备像素比=${devicePixelRatio}, 最终缩放=${scale.toFixed(
3
)}, canvas尺寸=${canvas.width}x${
canvas.height
}, 显示尺寸=${containerWidth}x${containerHeight}`
);
// 创建PDF页面视图
const rawPdfDocument = toRaw(pdfDocument.value);
const pdfPage = await rawPdfDocument.getPage(pageNum);
// 先计算缩放比例
const originalViewport = pdfPage.getViewport({ scale: 1.0 });
const scaleX = containerWidth / originalViewport.width;
const scaleY = containerHeight / originalViewport.height;
const scale = Math.min(scaleX, scaleY);
// 创建基于缩放比例的视口
const scaledViewport = pdfPage.getViewport({ scale: scale });
console.log(`PDF原始尺寸: ${originalViewport.width}x${originalViewport.height}, 缩放比例: ${scale}, 缩放后尺寸: ${scaledViewport.width}x${scaledViewport.height}`);
// 创建PDFPageView实例
if (!pdfjsViewer) {
throw new Error('PDF.js viewer not loaded');
}
const pageView = new pdfjsViewer.PDFPageView({
container: pageDiv,
id: pageNum - 1,
scale: 1.0, // 使用1.0,因为我们已经通过scaledViewport处理了缩放
defaultViewport: scaledViewport,
eventBus: toRaw(eventBus.value),
textLayerMode: 1, // 根据开关状态设置
annotationMode: 0, // 1=ENABLE
renderInteractiveForms: false,
renderer: "canvas",
enableXfa: false,
l10n: null,
textLayerFactory: new pdfjsViewer.DefaultTextLayerFactory()
});
// 渲染PDF页面
const context = canvas.getContext("2d");
const renderContext = {
canvasContext: context,
viewport: scaledViewport,
};
// 设置PDF页面
pageView.setPdfPage(pdfPage);
await page.render(renderContext).promise;
// 存储页面视图实例
pdfViewerInstances.value.set(pageIndex, pageView);
// 标记canvas已渲染
canvas.dataset.rendered = "true";
// 渲染页面
await pageView.draw();
// 标记页面已渲染
renderedPages.value.add(pageIndex);
// 渲染文字层(如果启用)
if (textLayerEnabled.value) {
try {
const textContent = await page.getTextContent();
// 创建文字层容器
const textLayer = document.createElement("div");
textLayer.className = "text-layer";
textLayer.id = `text-layer-${pageNum}`; // 添加唯一ID便于调试
textLayer.style.position = "absolute";
textLayer.style.left = "0";
textLayer.style.top = "0";
// 文字层尺寸应该和turn.js的页面尺寸一致,这样就能完全覆盖左右分页
// 从turn.js获取实际的页面尺寸
const $magazine = $(magazine.value);
let turnPageWidth, turnPageHeight;
if ($magazine.length && $magazine.turn) {
const turnSize = $magazine.turn("size");
if (turnSize && turnSize.width && turnSize.height) {
// 移动端使用单页尺寸,桌面端使用双页尺寸的一半
if (isMobile.value) {
turnPageWidth = turnSize.width;
turnPageHeight = turnSize.height;
} else {
turnPageWidth = turnSize.width / 2;
turnPageHeight = turnSize.height;
}
}
}
// 如果无法从turn.js获取尺寸,使用containerWidth和containerHeight
if (!turnPageWidth || !turnPageHeight) {
turnPageWidth = containerWidth;
turnPageHeight = containerHeight;
}
// 验证渲染结果
const canvas = pageDiv.querySelector('canvas');
const svg = pageDiv.querySelector('svg');
const renderElement = canvas || svg;
textLayer.style.width = `${turnPageWidth}px`;
textLayer.style.height = `${turnPageHeight}px`;
textLayer.style.pointerEvents = "auto";
textLayer.style.userSelect = "text";
textLayer.style.zIndex = "999"; // 使用更高的z-index
textLayer.style.backgroundColor = "transparent"; // 文字层背景透明
if (renderElement) {
const rect = renderElement.getBoundingClientRect();
const elementStyle = window.getComputedStyle(renderElement);
const containerStyle = window.getComputedStyle(pageDiv);
// 添加详细的尺寸调试信息
console.log(`=== 页面${pageNum}尺寸调试 ===`);
console.log(
`containerWidth: ${containerWidth}, containerHeight: ${containerHeight}`
);
console.log(`turn.js页面尺寸: ${turnPageWidth}x${turnPageHeight}`);
console.log(
`canvas.style.width: ${canvas.style.width}, canvas.style.height: ${canvas.style.height}`
);
console.log(
`canvas.clientWidth: ${canvas.clientWidth}, canvas.clientHeight: ${canvas.clientHeight}`
);
console.log(
`canvas.offsetWidth: ${canvas.offsetWidth}, canvas.offsetHeight: ${canvas.offsetHeight}`
);
console.log(
`pageContainer.clientWidth: ${pageContainer.clientWidth}, pageContainer.clientHeight: ${pageContainer.clientHeight}`
);
console.log(`PDF原始尺寸: ${viewport.width}x${viewport.height}`);
console.log(`缩放比例: ${baseScale}`);
console.log(`文字层尺寸: ${turnPageWidth}x${turnPageHeight}`);
console.log(`========================`);
// 专门打印文字层和PDF分页的尺寸对比
console.log(`🔍 尺寸对比检查:`);
console.log(` 文字层尺寸: ${turnPageWidth}x${turnPageHeight}`);
console.log(
` PDF分页尺寸: ${pageContainer.clientWidth}x${pageContainer.clientHeight}`
);
console.log(
` Canvas显示尺寸: ${canvas.style.width}x${canvas.style.height}`
);
console.log(
` 是否一致: ${
turnPageWidth === pageContainer.clientWidth &&
turnPageHeight === pageContainer.clientHeight
? "✅ 一致"
: "❌ 不一致"
}`
);
if (
turnPageWidth !== pageContainer.clientWidth ||
turnPageHeight !== pageContainer.clientHeight
) {
console.log(
` 差异: 宽度差${Math.abs(
turnPageWidth - pageContainer.clientWidth
)}px, 高度差${Math.abs(
turnPageHeight - pageContainer.clientHeight
)}px`
`PDF页面 ${pageNum} 渲染完成 (${renderElement.tagName}),尺寸: ${rect.width}x${rect.height}, 容器尺寸: ${containerWidth}x${containerHeight}`
);
}
// 不需要scale变换,直接使用turn.js的页面尺寸
textLayer.style.transform = "none";
textLayer.style.transformOrigin = "0 0";
if (canvas) {
console.log(`Canvas CSS尺寸: ${elementStyle.width}x${elementStyle.height}, 实际尺寸: ${canvas.width}x${canvas.height}`);
console.log(`文字层直接使用turn.js页面尺寸,无变换`);
// 确保canvas尺寸正确
if (Math.abs(rect.width - containerWidth) > 5 || Math.abs(rect.height - containerHeight) > 5) {
console.warn(`页面${pageNum}尺寸不匹配,Canvas: ${rect.width}x${rect.height}, 容器: ${containerWidth}x${containerHeight}`);
// 添加文字项
textContent.items.forEach((item, index) => {
// 简化文字定位,先确保能看到文字
const style = textContent.styles[item.fontName];
const fontSize = item.height * baseScale; // 应用缩放
const fontFamily = style ? style.fontFamily : "sans-serif";
// 尝试强制调整canvas尺寸
if (Math.abs(canvas.width - scaledViewport.width) > 5) {
console.log(`强制调整Canvas尺寸从 ${canvas.width}x${canvas.height}${scaledViewport.width}x${scaledViewport.height}`);
canvas.style.width = `${scaledViewport.width}px`;
canvas.style.height = `${scaledViewport.height}px`;
}
}
}
}
// 创建文字span元素
const textSpan = document.createElement("span");
textSpan.textContent = item.str;
textSpan.id = `text-${pageNum}-${index}`; // 添加唯一ID便于调试
textSpan.style.position = "absolute";
// 标记页面已加载
if (processedPages.value[pageIndex]) {
processedPages.value[pageIndex].isLoaded = true;
}
// 使用百分比方法计算文字在turn.js页面中的位置
// 先计算PDF中的百分比位置,再乘以分页尺寸
} catch (error) {
console.error(`渲染PDF页面 ${pageNum} 失败:`, error);
}
};
// X坐标:计算PDF中的水平百分比
const xPercent = item.transform[4] / viewport.width;
const xPos = xPercent * turnPageWidth;
// 重新调整PDF页面尺寸
const resizePdfPage = async (pageIndex) => {
if (!pdfViewerInstances.value.has(pageIndex)) return;
// Y坐标:计算PDF中的垂直百分比(需要翻转)
// PDF中的Y坐标是文字框的顶部位置
// 我们需要考虑文字框的高度来调整位置
const textHeight = item.height;
const pdfY = item.transform[5];
const pageNum = pageIndex + 1;
const pageContainer = magazine.value.querySelector(`.p${pageNum}`);
if (!pageContainer) return;
// 方法1:如果PDF的Y是文字框顶部,我们需要调整到文字基线位置
// 文字基线通常在文字框高度的80%位置
const baselineOffset = textHeight * 0.8; // 文字基线偏移
const adjustedPdfY = pdfY + baselineOffset;
const pageView = pdfViewerInstances.value.get(pageIndex);
if (!pageView) return;
// 计算调整后的百分比位置
const yPercent = 1 - adjustedPdfY / viewport.height;
let yPos = yPercent * turnPageHeight;
try {
// 获取新的容器尺寸
const containerWidth = pageContainer.clientWidth;
const containerHeight = pageContainer.clientHeight;
// 调试百分比计算
if (index < 3) {
console.log(`文字"${item.str}" 百分比计算:`);
console.log(
` PDF原始坐标: x=${item.transform[4]}, y=${item.transform[5]}`
);
console.log(` PDF原始Y: ${pdfY}, 文字高度: ${textHeight}`);
console.log(
` 基线偏移: ${baselineOffset.toFixed(2)} (文字高度的80%)`
);
console.log(` 调整后PDF Y: ${adjustedPdfY.toFixed(2)}`);
console.log(
` PDF百分比: x=${(xPercent * 100).toFixed(2)}%, y=${(
yPercent * 100
).toFixed(2)}%`
);
console.log(` turn.js尺寸: ${turnPageWidth}x${turnPageHeight}`);
console.log(
` 最终位置: x=${xPos.toFixed(2)}, y=${yPos.toFixed(2)}`
);
console.log(` Y坐标说明: 考虑了文字框高度和基线偏移`);
}
if (containerWidth === 0 || containerHeight === 0) return;
textSpan.style.left = `${xPos}px`;
textSpan.style.top = `${yPos}px`;
console.log(`重新调整页面${pageNum}尺寸: ${containerWidth}x${containerHeight}`);
textSpan.style.fontSize = `${fontSize}px`;
textSpan.style.fontFamily = fontFamily;
textSpan.style.fontWeight = style?.fontWeight || "normal";
textSpan.style.fontStyle = style?.fontStyle || "normal";
// 获取PDF页面和视口
const pdfPage = pageView.pdfPage;
const originalViewport = pdfPage.getViewport({ scale: 1.0 });
// 设置文字尺寸
textSpan.style.width = `${item.width * baseScale}px`;
textSpan.style.height = `${item.height * baseScale}px`;
textSpan.style.lineHeight = `${item.height * baseScale}px`;
// 计算新的缩放比例
const scaleX = containerWidth / originalViewport.width;
const scaleY = containerHeight / originalViewport.height;
const newScale = Math.min(scaleX, scaleY);
// 文字颜色设为透明,但可以选中和复制
textSpan.style.color = "transparent";
textSpan.style.cursor = "text";
textSpan.style.whiteSpace = "pre";
textSpan.style.overflow = "hidden";
textSpan.style.backgroundColor = "transparent"; // 文字背景透明
// 创建新的缩放视口
const newScaledViewport = pdfPage.getViewport({ scale: newScale });
// 添加选择事件支持
textSpan.addEventListener("mouseup", () => {
const selection = window.getSelection();
if (selection.toString()) {
console.log("选中文本:", selection.toString());
}
});
// 更新页面视图的视口和缩放比例
pageView.update(1.0, newScaledViewport);
textLayer.appendChild(textSpan);
// 重新渲染页面
await pageView.draw();
// 调试信息
if (index < 5) {
// 只显示前5个文字项的信息
console.log(
`文字项${index}: "${item.str}" 位置=(${xPos.toFixed(
2
)}, ${yPos.toFixed(2)}) 尺寸=${item.width * baseScale}x${
item.height * baseScale
}`
);
console.log(` - 原始transform: [${item.transform.join(", ")}]`);
console.log(
` - 百分比位置: x=${(xPercent * 100).toFixed(2)}%, y=${(
yPercent * 100
).toFixed(2)}%`
);
}
});
console.log(`页面${pageNum}尺寸调整完成,新缩放比例: ${newScale}`);
// 将文字层添加到页面容器中
if (pageContainer) {
// 移除已存在的文字层
const existingTextLayer = pageContainer.querySelector(".text-layer");
if (existingTextLayer) {
existingTextLayer.remove();
} catch (error) {
console.error(`调整页面${pageNum}尺寸失败:`, error);
}
};
// 确保文字层在canvas之上
pageContainer.appendChild(textLayer);
// 批量重新调整所有可见PDF页面尺寸
const resizeVisiblePdfPages = async () => {
if (!magazine.value || !isInitialized.value) return;
// 设置文字层的z-index,确保在canvas之上
textLayer.style.zIndex = "20";
}
const $magazine = $(magazine.value);
if (!$magazine.length || !$magazine.turn) return;
console.log(`文字层渲染完成,共 ${textContent.items.length} 个文字项`);
console.log(`文字层位置: left=0, top=0, width=100%, height=100%`);
console.log(`文字层z-index: ${textLayer.style.zIndex}`);
console.log(`文字层ID: ${textLayer.id}`);
try {
const currentPage = $magazine.turn("page");
// 使用processedPages的长度而不是turn.js的pages值
const totalPages = processedPages.value.length;
// 验证文字层是否真的添加到DOM中
const addedTextLayer = pageContainer.querySelector(
`#text-layer-${pageNum}`
);
if (addedTextLayer) {
console.log(`✅ 文字层已成功添加到DOM,ID: ${addedTextLayer.id}`);
console.log(`文字层子元素数量: ${addedTextLayer.children.length}`);
// 最终验证文字层的实际尺寸
const textLayerRect = addedTextLayer.getBoundingClientRect();
const pageContainerRect = pageContainer.getBoundingClientRect();
console.log(`🔍 最终尺寸验证:`);
console.log(
` 文字层实际尺寸: ${textLayerRect.width.toFixed(
2
)}x${textLayerRect.height.toFixed(2)}`
);
console.log(
` 页面容器实际尺寸: ${pageContainerRect.width.toFixed(
2
)}x${pageContainerRect.height.toFixed(2)}`
);
console.log(
` 是否完全一致: ${
Math.abs(textLayerRect.width - pageContainerRect.width) < 1 &&
Math.abs(textLayerRect.height - pageContainerRect.height) < 1
? "✅ 完全一致"
: "❌ 有差异"
}`
);
// 获取当前可见的页面索引
const visiblePages = [];
if (isMobile.value) {
// 移动端只显示当前页
visiblePages.push(currentPage - 1);
} else {
console.error(`❌ 文字层未能添加到DOM中!`);
}
} catch (textError) {
console.warn(`文字层渲染失败:`, textError);
// 桌面端显示当前页和下一页
visiblePages.push(currentPage - 1);
if (currentPage < totalPages) {
visiblePages.push(currentPage);
}
}
// 标记页面已渲染
if (processedPages.value[pageIndex]) {
processedPages.value[pageIndex].isLoaded = true;
// 重新调整可见页面的尺寸
for (const pageIndex of visiblePages) {
if (renderedPages.value.has(pageIndex)) {
await resizePdfPage(pageIndex);
}
// 检查canvas是否正确显示
const renderedCanvas = pageContainer.querySelector("canvas");
if (renderedCanvas) {
const rect = renderedCanvas.getBoundingClientRect();
console.log(
`PDF页面 ${pageNum} 渲染完成,canvas尺寸: ${rect.width}x${
rect.height
}, 位置: (${rect.left}, ${rect.top}), 可见性: ${
renderedCanvas.style.visibility || "visible"
}, 显示: ${renderedCanvas.style.display || "block"}`
);
} else {
console.warn(`PDF页面 ${pageNum} 渲染完成,但找不到canvas元素`);
}
} catch (error) {
console.error(`渲染PDF页面 ${pageNum} 失败:`, error);
console.error("重新调整PDF页面尺寸失败:", error);
}
};
......@@ -1215,7 +1149,8 @@ const updateZoom = () => {
// 保存当前可见页面的图片状态
const visiblePages = [];
const totalPages = $magazine.turn("pages");
// 使用processedPages的长度而不是turn.js的pages值
const totalPages = processedPages.value.length;
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
......@@ -1298,7 +1233,8 @@ const handleResize = async () => {
// 保存当前可见页面的图片状态
const visiblePages = [];
const totalPages = $magazine.turn("pages");
// 使用processedPages的长度而不是turn.js的pages值
const totalPages = processedPages.value.length;
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
......@@ -1392,43 +1328,112 @@ const toggleTextLayer = () => {
textLayerEnabled.value = !textLayerEnabled.value;
if (textLayerEnabled.value) {
console.log("启用文字层,重新渲染所有可见页面");
// 清除渲染状态,重新渲染所有可见页面
renderedPages.value.clear();
pdfViewerInstances.value.clear(); // 清除页面视图实例
const $magazine = $(magazine.value);
if ($magazine.length) {
const currentPage = $magazine.turn("page");
renderVisiblePdfPages(currentPage);
}
} else {
// 移除所有文字层
const textLayers = document.querySelectorAll(".text-layer");
textLayers.forEach((layer) => layer.remove());
console.log("禁用文字层,重新渲染所有可见页面");
// 清除渲染状态,重新渲染所有可见页面(不包含文字层)
renderedPages.value.clear();
pdfViewerInstances.value.clear(); // 清除页面视图实例
const $magazine = $(magazine.value);
if ($magazine.length) {
const currentPage = $magazine.turn("page");
renderVisiblePdfPages(currentPage);
}
}
// 保存设置到本地存储
localStorage.setItem("bookReaderTextLayer", textLayerEnabled.value);
};
// 添加清晰度切换函数
const toggleHighQuality = () => {
// 切换清晰度设置
const newQuality = !props.highQualityRendering;
// 添加渲染器切换函数
const toggleRenderer = () => {
svgRendererEnabled.value = !svgRendererEnabled.value;
console.log(`切换到${svgRendererEnabled.value ? 'SVG' : 'Canvas'}渲染模式,重新渲染所有可见页面`);
// 清除渲染状态,重新渲染所有可见页面
renderedPages.value.clear();
// 重新渲染当前可见页面
pdfViewerInstances.value.clear(); // 清除页面视图实例
const $magazine = $(magazine.value);
if ($magazine.length) {
const currentPage = $magazine.turn("page");
console.log(`切换清晰度设置: ${newQuality ? "高清晰度" : "标准清晰度"}`);
renderVisiblePdfPages(currentPage);
}
// 保存设置到本地存储
localStorage.setItem("bookReaderHighQuality", newQuality);
localStorage.setItem("bookReaderSvgRenderer", svgRendererEnabled.value);
};
// 专门处理turn.js坐标变换的函数
const fixTurnJsCoordinateSystem = () => {
try {
const allPages = document.querySelectorAll('.pdf-page-container');
let totalFixed = 0;
allPages.forEach(pageDiv => {
const textLayer = pageDiv.querySelector('.textLayer');
if (!textLayer) return;
const textDivs = textLayer.querySelectorAll('div');
const container = pageDiv.closest('.pdf-page-container');
const turnPage = container ? container.closest('.page') : null;
if (!turnPage || !container) return;
const pageRect = turnPage.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// 计算turn.js的缩放比例
const scaleX = pageRect.width / containerRect.width;
const scaleY = pageRect.height / containerRect.height;
textDivs.forEach(div => {
// 获取原始位置数据
const calculatedPos = div.getAttribute('data-calculated-pos');
if (calculatedPos) {
try {
const pos = JSON.parse(calculatedPos);
// 应用turn.js的缩放修正
const correctedX = pos.x * scaleX;
const correctedY = pos.y * scaleY;
// 强制应用修正后的位置
div.style.left = `${correctedX}px !important`;
div.style.top = `${correctedY}px !important`;
// 更新CSS变量
div.style.setProperty('--text-left', `${correctedX}px`);
div.style.setProperty('--text-top', `${correctedY}px`);
totalFixed++;
} catch (e) {
console.error('解析位置数据失败:', e);
}
}
});
});
if (totalFixed > 0) {
console.log(`修正了 ${totalFixed} 个文本元素的turn.js坐标变换`);
}
return totalFixed;
} catch (error) {
console.error('修正turn.js坐标变换失败:', error);
return 0;
}
};
// 添加调试函数:检查页面状态
const debugPageStatus = () => {
if (!magazine.value) return;
......@@ -1444,35 +1449,35 @@ const debugPageStatus = () => {
Array.from(renderedPages.value).map((i) => i + 1)
);
// 检查当前页面的canvas状态
// 检查当前页面的PdfViewer状态
const currentPageContainer = magazine.value.querySelector(`.p${currentPage}`);
if (currentPageContainer) {
const canvas = currentPageContainer.querySelector("canvas");
if (canvas) {
const rect = canvas.getBoundingClientRect();
const pageView = pdfViewerInstances.value.get(currentPage - 1);
if (pageView) {
// 使用更安全的方式检查状态,避免访问私有字段
try {
const hasTextLayer = pageView.textLayer && typeof pageView.textLayer === 'object';
const hasAnnotationLayer = pageView.annotationLayer && typeof pageView.annotationLayer === 'object';
console.log(
`当前页面canvas状态: 尺寸=${rect.width}x${rect.height}, 位置=(${
rect.left
}, ${rect.top}), 可见性=${canvas.style.visibility || "visible"}, 显示=${
canvas.style.display || "block"
}`
`当前页面PdfViewer状态: 实例存在=true, 文字层=${hasTextLayer ? '启用' : '禁用'}, 注释层=${hasAnnotationLayer ? '启用' : '禁用'}`
);
} catch (error) {
console.log(`当前页面PdfViewer状态: 实例存在=true, 状态检查失败=${error.message}`);
}
} else {
console.log(`当前页面没有canvas元素`);
console.log(`当前页面没有PdfViewer实例`);
}
}
// 检查所有页面的状态
for (let i = 1; i <= totalPages; i++) {
const pageContainer = magazine.value.querySelector(`.p${i}`);
if (pageContainer) {
const canvas = pageContainer.querySelector("canvas");
const pageView = pdfViewerInstances.value.get(i - 1);
const isRendered = renderedPages.value.has(i - 1);
console.log(
`页面${i}: 容器存在=${!!pageContainer}, canvas存在=${!!canvas}, 已渲染=${isRendered}`
`页面${i}: 容器存在=${!!pageContainer}, PdfViewer实例存在=${!!pageView}, 已渲染=${isRendered}`
);
}
}
console.log(`====================`);
};
......@@ -1486,8 +1491,22 @@ window.addEventListener("resize", () => {
});
onMounted(async () => {
// 配置PDF.js worker
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.mjs";
// 先加载PDF.js
await loadPdfJs();
// 配置PDF.js worker - 使用标准的worker文件而不是.mjs
if (pdfjsLib && pdfjsLib.GlobalWorkerOptions) {
// 使用pdfjs-dist/build目录下的标准worker文件
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.js";
}
// 初始化PdfViewer相关服务
if (pdfjsViewer) {
eventBus.value = new pdfjsViewer.EventBus();
pdfLinkService.value = new pdfjsViewer.PDFLinkService({
eventBus: eventBus.value,
});
}
// 从本地存储读取静音状态
const savedMuted = localStorage.getItem("bookReaderMuted");
......@@ -1527,6 +1546,26 @@ onUnmounted(() => {
renderedPages.value.clear(); // 清除PDF渲染状态
pdfInitialized.value = false; // 清除PDF初始化状态
// 清理PdfViewer实例
pdfViewerInstances.value.forEach((pageView) => {
if (pageView && typeof pageView.destroy === 'function') {
try {
pageView.destroy();
} catch (error) {
console.warn('清理PdfViewer实例时出错:', error);
}
}
});
pdfViewerInstances.value.clear();
// 清理事件总线和链接服务
if (eventBus.value) {
eventBus.value = null;
}
if (pdfLinkService.value) {
pdfLinkService.value = null;
}
// 重置状态
initialLoading.value = true;
......@@ -1593,10 +1632,18 @@ const loadDirectoryImages = () => {
}));
};
const goToPage = (pageNum) => {
const goToPage = async (pageNum) => {
if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value);
$magazine.turn("page", pageNum);
// 等待turn.js完成页面跳转
await new Promise((resolve) => setTimeout(resolve, 100));
// 如果是PDF模式,重新调整页面尺寸
if (props.isPdf && pdfInitialized.value) {
await resizeVisiblePdfPages();
}
}
};
......@@ -1675,13 +1722,13 @@ const showGuide = () => {
transform-style: preserve-3d;
}
/* PDF页面样式 - 支持PdfViewer */
.page {
background-color: white;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
// user-select: none;
visibility: visible !important;
backface-visibility: visible;
transform-style: preserve-3d;
......@@ -1689,76 +1736,6 @@ const showGuide = () => {
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.05);
}
/* PDF页面canvas样式 */
.page-canvas {
width: 100%;
height: 100%;
display: block;
background-color: white;
/* 确保canvas在高分辨率下清晰显示 */
image-rendering: -webkit-optimize-contrast;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
image-rendering: pixelated;
/* 防止浏览器自动缩放 */
image-rendering: auto;
}
/* PDF文字层样式 */
.text-layer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1;
pointer-events: auto;
user-select: text;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
z-index: 10;
}
.text-layer > span {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
}
.text-layer .highlight {
margin: -1px;
padding: 1px;
background-color: rgb(180, 0, 170);
border-radius: 4px;
}
.text-layer .highlight.begin {
border-radius: 4px 0px 0px 4px;
}
.text-layer .highlight.end {
border-radius: 0px 4px 4px 0px;
}
.text-layer .highlight.middle {
border-radius: 0px;
}
.text-layer .highlight.selected {
background-color: rgb(0, 100, 0);
}
/* 确保文字层在turn.js翻页时保持可见 */
.page .text-layer {
transform-style: preserve-3d;
backface-visibility: visible;
}
.page-canvas.wide-canvas {
width: 100%;
height: 100%;
......
<template>
<pdf-viewer
url="/static/c4cfd7ec74e4d2d512f71a3b09c3d700/c4cfd7ec74e4d2d512f71a3b09c3d700.pdf"
url="/static/393545343da86f2290800e0c570850a8/393545343da86f2290800e0c570850a8.pdf"
/>
</template>
<script setup>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论