123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- <template>
- <div class="pdf-container">
- <div class="header">
- <div class="title">{{ title }}</div>
- <div class="close-icon" @click="emits('close')">
- <CloseOutlined />
- </div>
- </div>
- <div ref="pdfRef" class="pdf-viewer"></div>
- </div>
- </template>
- <script setup>
- import { CloseOutlined } from '@ant-design/icons-vue';
- import { ref, watch } from 'vue';
- import PdfjsWorker from 'pdfjs-dist/build/pdf.worker.js?worker';
- const emits = defineEmits(['close']);
- const props = defineProps({
- src: { type: String, required: true },
- content: { type: String, required: true },
- num: { type: Number, required: false }
- });
- const pdfRef = ref(null);
- const height = ref(0);
- const start = ref(0);
- const pageContent = ref([]);
- const pageText = ref([]);
- // 侦听props的src改变,则立即切换渲染pdf
- watch(
- () => props.src,
- () => {
- setTimeout(() => init(), 1);
- },
- { immediate: true }
- );
- const title = computed(() => {
- return window.formatDocName(decodeURI(decodeURI(props.src)))
- // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
- });
- // 渲染pdf
- async function init() {
- var pdfDom = document.getElementsByClassName('pdf-viewer')[0];
- while (pdfDom.firstChild) {
- pdfDom.removeChild(pdfDom.firstChild);
- }
- // 异步加载pdf.js
- const PDFJS = await import('pdfjs-dist/build/pdf.js');
- if (typeof window !== 'undefined' && 'Worker' in window) {
- PDFJS.GlobalWorkerOptions.workerPort = new PdfjsWorker();
- }
- // 加载文档
- let loadingTask = PDFJS.getDocument({ url: props.src });
- loadingTask.__PDFDocumentLoadingTask = true;
- pageContent.value = [];
- const pdf = await loadingTask.promise; // 使用await等待加载完毕
- // 循环渲染每一页
- for (let i = 1; i <= pdf.numPages; i++) {
- const page = await pdf.getPage(i);
- await renderPage(page, PDFJS, i);
- }
- console.log('pdf页面全部渲染完毕', pdf.numPages, pdf);
- pageText.value = [];
- //获取每页数据
- var textDivs = document.getElementsByClassName('textLayer');
- if (textDivs) {
- for (var i = 0; i < textDivs.length; i++) {
- var item = { page: i + 1, txt: textDivs[i].innerText };
- pageText.value.push(item);
- }
- }
- if (props.content) {
- goLocation(props.content);
- }
- }
- const goLocation = (txt) => {
- //获取页面
- var pageNumber = 0;
- var text =txt;
- var ptText = text.replaceAll('\n', '');
- ptText = ptText.replaceAll(' ', '');
- pageText.value.forEach((item) => {
- item.txt = item.txt.replaceAll('\n', '');
- item.txt = item.txt.replaceAll(' ', '');
- console.log(item.txt);
- if (item.txt.indexOf(ptText.substring(0, 18)) > -1) {
- pageNumber = item.page;
- }
- });
- text = text.replaceAll('\n', '');
- text = text.replaceAll(' ', '');
- if (text.endsWith('。') || text.endsWith(',') || text.endsWith('、')) {
- text = text.substring(0, text.length - 1);
- }
- //'left: 133.17px; top: 152.408px; font-size: 31.3597px; font-family: sans-serif; transform: scaleX(1.01878);'
- var arr = document
- .getElementsByClassName('textLayer')
- [pageNumber - 1].getElementsByTagName('span');
- if (arr) {
- var hightList = [];
- var startIndex = 0;
- var endIndex = 0;
- startIndex = getStartIndex(arr, text, text.length);
- endIndex = getEndIndex(arr, text, text.length, startIndex);
- var currentPageIndex = 0;
- var nextArr = [];
- if (endIndex == undefined) {
- currentPageIndex = arr.length;
- nextArr = document
- .getElementsByClassName('textLayer')
- [pageNumber].getElementsByTagName('span');
- endIndex = getEndIndex(nextArr, text, text.length, startIndex);
- } else {
- currentPageIndex = endIndex;
- }
- for (var i = startIndex; i < currentPageIndex; i++) {
- var element = arr[i];
- var hItem = {};
- var cssText = element.style.cssText;
- var spanText = element.innerText;
- console.log(spanText);
- if (text.includes(spanText)) {
- var topLabel = cssText.split('top: ')[1].split(';')[0];
- topLabel = topLabel.substring(0, topLabel.length - 2);
- var currentItems = [];
- pageContent.value.forEach((item) => {
- if (item.page == pageNumber) {
- currentItems = item.items;
- }
- });
- for (var m = 0; m < currentItems.length; m++) {
- var citem = currentItems[m];
- if (citem.str == spanText) {
- hItem.width = parseFloat(citem.width);
- hItem.height = parseFloat(citem.height);
- hItem.text = spanText;
- hItem.left = parseFloat(cssText.substring(6, cssText.indexOf('px')));
- hItem.top = parseFloat(topLabel);
- hightList.push(hItem);
- if (text.endsWith(citem.str)) {
- break;
- }
- }
- }
- }
- }
- var nextHightList = [];
- //跨页 目前只考虑跨1页
- if (endIndex != undefined) {
- for (var i = 0; i < nextArr.length; i++) {
- var element = nextArr[i];
- var hItem = {};
- var cssText = element.style.cssText;
- var spanText = element.innerText;
- console.log(spanText);
- if (text.includes(spanText)) {
- var topLabel = cssText.split('top: ')[1].split(';')[0];
- topLabel = topLabel.substring(0, topLabel.length - 2);
- var currentItems = [];
- pageContent.value.forEach((item) => {
- if (item.page == pageNumber + 1) {
- currentItems = item.items;
- }
- });
- for (var l = 0; l < currentItems.length; l++) {
- var citem = currentItems[l];
- if (citem.str == spanText) {
- hItem.width = parseFloat(citem.width);
- hItem.height = parseFloat(citem.height);
- hItem.text = spanText;
- hItem.left = parseFloat(cssText.substring(6, cssText.indexOf('px')));
- hItem.top = parseFloat(topLabel);
- nextHightList.push(hItem);
- if (text.endsWith(citem.str)) {
- break;
- }
- }
- }
- }
- if (text.endsWith(spanText)) {
- break;
- }
- }
- }
- //定位
- pdfRef.value.scrollTop = height.value * (pageNumber - 1) + hightList[0]['top'];
- //渲染
- var canvas = document.getElementsByClassName(`page`)[pageNumber - 1];
- const hightDiv = document.createElement('div');
- hightDiv.className = 'highlight-layer flash';
- hightList.forEach((it) => {
- const hDiv = document.createElement('div');
- hDiv.className = 'highlight';
- // 设置内联样式
- hDiv.style.width = it.width * 1.5 + 'px';
- hDiv.style.height = it.height * 1.5 + 'px';
- hDiv.style.left = it.left + 'px';
- hDiv.style.top = it.top + 'px';
- hightDiv.appendChild(hDiv);
- });
- canvas.appendChild(hightDiv);
- if (nextHightList && nextHightList.length > 0) {
- //渲染
- var nextCanvas = document.getElementsByClassName(`page`)[pageNumber];
- const hightDiv = document.createElement('div');
- hightDiv.className = 'highlight-layer flash';
- nextHightList.forEach((it) => {
- const hDiv = document.createElement('div');
- hDiv.className = 'highlight';
- // 设置内联样式
- hDiv.style.width = it.width * 1.5 + 'px';
- hDiv.style.height = it.height * 1.5 + 'px';
- hDiv.style.left = it.left + 'px';
- hDiv.style.top = it.top + 'px';
- hightDiv.appendChild(hDiv);
- });
- nextCanvas.appendChild(hightDiv);
- }
- }
- };
- const getStartIndex = (arr, text, total) => {
- var count = 0;
- for (var i = 0; i < arr.length; i++) {
- var element = arr[i];
- var spanText = element.innerText;
- console.log('===' + spanText);
- if (text.startsWith(spanText)) {
- return i;
- } else {
- count++;
- }
- }
- if (count == arr.length) {
- start.value = start.value + 1;
- text = text.substring(start.value, total);
- getStartIndex(arr, text, total);
- }
- };
- const getEndIndex = (arr, text, total, startIndex) => {
- for (var i = 0; i < arr.length; i++) {
- var element = arr[i];
- var spanText = element.innerText;
- console.log('===' + spanText);
- if (text.endsWith(spanText) && spanText.length > 1) {
- return i;
- }
- }
- };
- const renderPage = (page, PDFJS, num) => {
- return new Promise(async (resolve, reject) => {
- // 先渲染图片
- let pixelRatio = 1;
- const viewport = page.getViewport({ scale: 1.5 });
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- canvas.id = `page-${num}`;
- canvas.height = viewport.height * pixelRatio;
- console.log('==height', canvas.height);
- height.value = canvas.height;
- canvas.width = viewport.width * pixelRatio;
- const renderContext = {
- canvasContext: context,
- viewport: viewport,
- transform: [pixelRatio, 0, 0, pixelRatio, 0, 0]
- };
- await page.render(renderContext).promise;
- // 再渲染文字
- const textContent = await page.getTextContent();
- pageContent.value.push({ page: num, items: textContent.items });
- const textLayer = document.createElement('div');
- PDFJS.renderTextLayer({
- textContent: textContent,
- container: textLayer,
- viewport: viewport,
- transform: [pixelRatio, 0, 0, pixelRatio, 0, 0],
- textDivs: []
- });
- textLayer.className = 'textLayer';
- // 将文字覆盖在图片上
- const canvasWrapper = document.createElement('div');
- canvasWrapper.className = 'canvasWrapper';
- const pageDiv = document.createElement('div');
- pageDiv.className = 'page';
- canvasWrapper.appendChild(canvas);
- pageDiv.appendChild(canvasWrapper);
- pageDiv.appendChild(textLayer);
- pdfRef.value.appendChild(pageDiv);
- resolve();
- });
- };
- </script>
- <style scoped lang="scss">
- .pdf-container {
- width: 100%;
- height: 100%;
- position: relative;
- .header {
- height: 40px;
- position: sticky;
- display: flex;
- justify-content: center;
- align-items: center;
- border-bottom: 1px solid #eee;
- top: 0px;
- z-index: 9999999;
- background: #fff;
- width: 100%;
- right: 0px;
- left: 0px;
- .title {
- font-size: 18px;
- font-weight: bolder;
- text-overflow: ellipsis;
- width: 0px;
- white-space: nowrap;
- flex-grow: 1;
- overflow: hidden;
- margin-left: 30px;
- margin-right: 40px;
- }
- .close-icon {
- position: absolute;
- top: 10px;
- right: 21px;
- cursor: pointer;
- }
- }
- }
- .pdf-viewer {
- width: 100%;
- height: calc(100% - 40px);
- position: relative;
- border: 0px solid #ccc;
- overflow-y: auto;
- }
- </style>
- <style>
- /* 不能用scoped,因为动态创建的元素不会被编译带上hash */
- /* 不加上这些css修饰,元素会错位,样式来自vue-pdf-embed这个库 */
- .canvasWrapper {
- position: relative;
- /* display: flex;
- justify-content: center; */
- }
- .page {
- position: relative;
- box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.08);
- margin: 30px auto;
- }
- .textLayer {
- text-align: initial;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
- opacity: 0.2;
- line-height: 1;
- text-size-adjust: none;
- forced-color-adjust: none;
- }
- .textLayer span,
- .textLayer br {
- color: transparent;
- position: absolute;
- white-space: pre;
- cursor: text;
- transform-origin: 0% 0%;
- }
- .highlight-layer {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- pointer-events: none;
- opacity: 0.15;
- overflow: hidden;
- }
- .flash {
- animation: flashing 0.6s;
- animation-iteration-count: 2;
- }
- @keyframes flashing {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 0.15;
- }
- }
- .highlight {
- background-color: #0076fa;
- position: absolute;
- }
- </style>
|