<script setup>
import { ref, onMounted, computed } from 'vue';
import * as pdfjsLib from 'pdfjs-dist/build/pdf';
import { useDraggable } from '@vueuse/core';
import { PDFDocument,degrees } from 'pdf-lib';
import img2 from '@/assets/222.jpg';
// 这里的文件从node_modules/pdfjs-dist/build/目录下复制到public目录下
pdfjsLib.GlobalWorkerOptions.workerSrc = `./pdf.worker.mjs`;
const pdfCanvases = ref([]);
const pageCount = ref(0);
const pdfCanvasContainer = ref(null);
const rotation = ref(0); // 添加旋转角度变量
const imgRef = ref(null);
const pdfWidth = ref(0);
const pdfHeight = ref(0);
const pdfTotalHeight = ref(0);
const myImage = ref(null);
const imageWidth = ref(0);
const imageHeight = ref(0);
// 存储初始位置
const initialX = ref(0);
const initialY = ref(0);
// 最后停留的位置
const finalX = ref(0);
const finalY = ref(0);
const getImageSize = () => {
if (myImage.value) {
imageWidth.value = myImage.value.naturalWidth;
imageHeight.value = myImage.value.naturalHeight;
}
};
const { x, y, style } = useDraggable(imgRef);
// 计算考虑滚动偏移后的样式
const draggableStyle = computed(() => {
let newX = x.value + window.scrollX;
let newY = y.value + window.scrollY;
// 边界检查,直接限制在 PDF 边界内
newX = Math.max(0, Math.min(newX, pdfWidth.value));
newY = Math.max(0, Math.min(newY, pdfTotalHeight.value));
if (newX + imageWidth.value >= pdfWidth.value) {
newX = pdfWidth.value - imageWidth.value;
}
if (newY + imageHeight.value >= pdfTotalHeight.value) {
newY = pdfTotalHeight.value - imageHeight.value;
}
finalX.value = newX;
finalY.value = newY;
return {
transform: `translate(${newX}px, ${newY}px)`,
};
});
onMounted(() => {
loadPdf('download.pdf');
// 获取初始位置
initialX.value = x.value;
initialY.value = y.value;
});
const loadPdf = async (url) => {
try {
// 清除旧的 canvas
if (pdfCanvasContainer.value) {
while (pdfCanvasContainer.value.firstChild) {
pdfCanvasContainer.value.removeChild(pdfCanvasContainer.value.firstChild);
}
}
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
pageCount.value = pdf.numPages;
// 清空画布数组
pdfCanvases.value = [];
for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {
const page = await pdf.getPage(pageNumber);
const scale = 1;
let viewport = page.getViewport({ scale });
// 应用旋转
viewport = page.getViewport({ scale, rotation: rotation.value });
const canvas = document.createElement('canvas'); // 直接创建 canvas 元素
canvas.height = viewport.height;
canvas.width = viewport.width;
pdfCanvases.value.push(canvas); // 将创建的 canvas 添加到数组中
const context = canvas.getContext('2d');
const renderContext = {
canvasContext: context,
viewport,
};
await page.render(renderContext).promise;
// 获取第一页的尺寸作为 PDF 预览区域的尺寸
if (pageNumber === 1) {
pdfWidth.value = viewport.width;
pdfHeight.value = viewport.height;
pdfTotalHeight.value = viewport.height * pdf.numPages;
}
}
// 将动态创建的 canvas 添加到模板中
if (pdfCanvasContainer.value) {
pdfCanvases.value.forEach((canvas) => {
pdfCanvasContainer.value.appendChild(canvas);
});
}
} catch (error) {
console.error('Error loading PDF:', error);
}
};
// 恢复初始位置
const resetPosition = () => {
x.value = initialX.value;
y.value = initialY.value;
};
const generatePdf = async () => {
try {
const pdfBytes = await fetch('download.pdf').then((res) => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(pdfBytes);
const imageBytes = await fetch(img2).then((res) => res.arrayBuffer());
// 检测 MIME 类型
const mimeType = await detectMimeType(imageBytes);
let image;
if (mimeType === 'image/jpeg') {
image = await pdfDoc.embedJpg(imageBytes);
} else if (mimeType === 'image/png') {
image = await pdfDoc.embedPng(imageBytes);
} else {
console.error('Unsupported image format.');
return;
}
const pages = pdfDoc.getPages();
const firstPage = pages[Math.floor(finalY.value / pdfHeight.value)]; // 将图片添加到第一页,你可以根据需要修改
pages.forEach(page=>{
page.setRotation(degrees(rotation.value));
})
// 计算图片相对于当前页面的 y 坐标
const pageY = finalY.value % pdfHeight.value;
firstPage.drawImage(image, {
x: rotation.value === 90?(firstPage.getHeight() - pageY - imageHeight.value):finalX.value,
y: rotation.value === 90?finalX.value:firstPage.getHeight() - pageY - imageHeight.value, // 注意:pdf-lib 的坐标系原点在左下角
width: imageWidth.value,
height: imageHeight.value,
});
const modifiedPdfBytes = await pdfDoc.save();
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'merged.pdf';
link.click();
} catch (error) {
console.error('Error generating PDF:', error);
}
};
// 检测 MIME 类型的辅助函数
const detectMimeType = (arrayBuffer) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const uint8Array = new Uint8Array(reader.result);
let mimeType = '';
if (uint8Array[0] === 0xff && uint8Array[1] === 0xd8 && uint8Array[2] === 0xff) {
mimeType = 'image/jpeg';
} else if (
uint8Array[0] === 0x89 &&
uint8Array[1] === 0x50 &&
uint8Array[2] === 0x4e &&
uint8Array[3] === 0x47
) {
mimeType = 'image/png';
} else {
mimeType = 'unknown';
}
resolve(mimeType);
};
reader.onerror = reject;
// 将 ArrayBuffer 转换为 Blob
const blob = new Blob([arrayBuffer.slice(0, 4)]);
reader.readAsArrayBuffer(blob);
});
};
const rota = ()=>{
loadPdf('download.pdf');
}
</script>
<template>
<div>
<div style="position: absolute;left: 50px">
<div>
{{ style }}
</div>
<div>
<button @click="resetPosition">恢复初始位置</button>
<button @click="generatePdf">生成 PDF</button>
</div>
<div>
<label>旋转角度:</label>
<input type="number" v-model.number="rotation" step="90" />
<button @click="rota">转换</button>
</div>
</div>
<div style="position: absolute" ref="imgRef" :style="draggableStyle">
<img ref="myImage" src="@/assets/222.jpg" alt="静态图片" @load="getImageSize" />
</div>
<div ref="pdfCanvasContainer"></div>
</div>
</template>
<style scoped></style>