vue project realizes pdf Online Preview

Pdf has been used for PDF preview before JS, I want to simplify this time. I decided to use Vue PDF. Although it is not as powerful as native, it has met common requirements. The important thing is that this component is easy to use. Now let's get to the point!
1, Install Vue pdf

npm install --save vue-pdf

2, Register on the required page

<script> 
import PDF from 'vue-pdf'
export default {
  components:{
      PDF,
  },
  data(){
      return {

      }
  }
</script>   

3, In general, there are many pages when previewing pdf. In order to improve performance, you can make a paging. Page: the number of pages currently displayed, such as page 1; rotate: rotation angle. For example, 0 means no rotation, and + 90, - 90 means horizontal rotation. Progress: the loading progress of the current page. The range is 0-1. When it is equal to 1, it means that the current page has been completely loaded. Page loaded: the callback function for successful page loading, which is not very useful. Num pages: total pages; Error: callback for loading error link clicked: the link in the stand-alone pdf will be triggered. Print this is the print function. (Note: garbled code will appear during Google browser preview. This problem is caused by the use of custom fonts in your pdf. To solve the problem, you need to replace node_modules / Vue pdf / SRC / pdfjsWrapper.js, I will put the replaced pdfjsWrapper.js at the end of the article.)

    <!-- preview PDF -->
    <el-dialog v-dialogDrag  :visible.sync="previewDialog">
      <template>
        <div>
          <div class="tools">
            <el-button :theme="'default'" type="submit" :title="'previous page'" @click.stop="prePage" class="mr10"> previous page</el-button>
            <el-button :theme="'default'" type="submit" :title="'next page'" @click.stop="nextPage" class="mr10"> next page</el-button>
            <div class="page">{{pageNum}}/{{pageTotalNum}} </div>
            <el-button :theme="'default'" type="submit" :title="'Clockwise rotation'" @click.stop="clock" class="mr10"> Clockwise rotation</el-button>
            <el-button :theme="'default'" type="submit" :title="'Counterclockwise rotation'" @click.stop="counterClock" class="mr10"> Counterclockwise rotation</el-button>
            <el-button :theme="'default'" type="submit" :title="'Print'" @click.stop="pdfPrintAll" class="mr10"> Print</el-button>
          </div>
          <pdf ref="pdf" :src="url" :page="pageNum" :rotate="pageRotate" @progress="loadedRatio = $event" @page-loaded="pageLoaded($event)" 
            @num-pages="pageTotalNum=$event" @error="pdfError($event)"  @link-clicked="page = $event"></pdf>
        </div>
      </template>
    </el-dialog>
<script> 
import PDF from 'vue-pdf'
export default {
  components:{
      PDF,
  },
  data(){
      return {
      	previewDialog:false,
      	url: "http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf",
      	pageNum: 1,
      	pageTotalNum: 1,
      	pageRotate: 0,
      	// Loading progress
      	loadedRatio: 0,
      	curPageNum: 0,
      },
      methods:{
    /**
     * Preview PDF
     */
    previewPDF(row,index) {
      this.previewDialog = true;
      console.log("", row,index);
      
    },
    // Function on previous page,
    prePage() {
      var page = this.pageNum
      page = page > 1 ? page - 1 : this.pageTotalNum
      this.pageNum = page
    },
    // Next function
    nextPage() {
      var page = this.pageNum
      page = page < this.pageTotalNum ? page + 1 : 1
      this.pageNum = page
    },
    // The page flips 90 degrees clockwise.
    clock() {
      this.pageRotate += 90
    },
    // The page flips 90 degrees counterclockwise.
    counterClock() {
      this.pageRotate -= 90
    },
    // Page load callback function, where e is the current number of pages
    pageLoaded(e) {
      this.curPageNum = e
    },
    // Callback function on error.
    pdfError(error) {
      console.error(error)
    },
    // Print all
    pdfPrintAll() {
      /**
       * The characters in the printing interface are garbled because you use a custom font in pdf. When Google browser prints, the preview interface really becomes true square characters. The solution is as follows:
       * Use the pdfjswrapper at the end of the article JS replacing node_modules/vue-pdf/src/pdfjsWrapper.js
       */
      console.log("Print");
      this.$refs.pdf.print()
    },

	 },
  }
</script>   

4, Load local pdf file
If you need to load local pdf files (no configuration is required), you need to configure flie loader locally. Otherwise, webpack cannot compile pdf files. The configuration method is also very simple. Find vue.exe in the project root directory config. JS file (if not, create a vue.config.js in the root directory). After that, there will be no problem with url:require("... / assets/xxx.pdf"). Note that Vue pdf SRC receives string objects. If you send an error directly to the url, you need to send the url Default.
Install file loader first

npm install --save file-loader

Then in Vue config. JS add the following content:

module.exports = {
    chainWebpack: config => {
        const fileRule = config.module.rule('file')
        fileRule.uses.clear()
        fileRule
            .test(/\.pdf|ico$/)
            .use('file-loader')
            .loader('file-loader')
            .options({
                limit: 10000,
            })
    },
    publicPath: './'
}

5, Solve the problem of pdf using custom font preview and printing garbled Code: pdfjswrapper js

import { PDFLinkService } from 'pdfjs-dist/es5/web/pdf_viewer';

var pendingOperation = Promise.resolve();

export default function(PDFJS) {

	function isPDFDocumentLoadingTask(obj) {

		return typeof(obj) === 'object' && obj !== null && obj.__PDFDocumentLoadingTask === true;
		// or: return obj.constructor.name === 'PDFDocumentLoadingTask';
	}

	function createLoadingTask(src, options) {

		var source;
		if ( typeof(src) === 'string' )
			source = { url: src };
		else if ( src instanceof Uint8Array )
			source = { data: src };
		else if ( typeof(src) === 'object' && src !== null )
			source = Object.assign({}, src);
		else
			throw new TypeError('invalid src type');

		// source.verbosity = PDFJS.VerbosityLevel.INFOS;
		// source.pdfBug = true;
		// source.stopAtErrors = true;

		if ( options && options.withCredentials )
			source.withCredentials = options.withCredentials;

		var loadingTask = PDFJS.getDocument(source);
		loadingTask.__PDFDocumentLoadingTask = true; // since PDFDocumentLoadingTask is not public

		if ( options && options.onPassword )
			loadingTask.onPassword = options.onPassword;

		if ( options && options.onProgress )
			loadingTask.onProgress = options.onProgress;

		return loadingTask;
	}


	function PDFJSWrapper(canvasElt, annotationLayerElt, emitEvent) {

		var pdfDoc = null;
		var pdfPage = null;
		var pdfRender = null;
		var canceling = false;

		canvasElt.getContext('2d').save();

		function clearCanvas() {

			canvasElt.getContext('2d').clearRect(0, 0, canvasElt.width, canvasElt.height);
		}

		function clearAnnotations() {

			while ( annotationLayerElt.firstChild )
				annotationLayerElt.removeChild(annotationLayerElt.firstChild);
		}

		this.destroy = function() {

			if ( pdfDoc === null )
				return;

			// Aborts all network requests and destroys worker.
			pendingOperation = pdfDoc.destroy();
			pdfDoc = null;
		}

		this.getResolutionScale = function() {

			return canvasElt.offsetWidth / canvasElt.width;
		}

		this.printPage = function(dpi, pageNumberOnly) {

			if ( pdfPage === null )
				return;

			// 1in == 72pt
			// 1in == 96px
			var PRINT_RESOLUTION = dpi === undefined ? 150 : dpi;
			var PRINT_UNITS = PRINT_RESOLUTION / 72.0;
			var CSS_UNITS = 96.0 / 72.0;

			var printContainerElement = document.createElement('div');
			printContainerElement.setAttribute('id', 'print-container')

			function removePrintContainer() {
				printContainerElement.parentNode.removeChild(printContainerElement);
			}

			new Promise(function(resolve, reject) {
				printContainerElement.frameBorder = '0';
				printContainerElement.scrolling = 'no';
				printContainerElement.width = '0px;'
				printContainerElement.height = '0px;'
				printContainerElement.style.cssText = 'position: absolute; top: 0; left: 0';

				window.document.body.appendChild(printContainerElement);
				resolve(window)
			})
			.then(function(win) {

				win.document.title = '';

				return pdfDoc.getPage(1)
				.then(function(page) {

					var viewport = page.getViewport({ scale: 1 });
					printContainerElement.appendChild(win.document.createElement('style')).textContent =
					'@supports ((size:A4) and (size:1pt 1pt)) {' +
							'@page { margin: 1pt; size: ' + ((viewport.width * PRINT_UNITS) / CSS_UNITS) + 'pt ' + ((viewport.height * PRINT_UNITS) / CSS_UNITS) + 'pt; }' +
						'}' +
						'#print-canvas { display: none }' +

						'@media print {' +
							'body { margin: 0 }' +
							'#print-canvas { page-break-before: avoid; page-break-after: always; page-break-inside: avoid; display: block }' +
							'body > *:not(#print-container) { display: none; }' +
							'}'+

						'@media screen {' +
							'body { margin: 0 }' +
						'}'
					return win;
				})
			})
			.then(function(win) {

				var allPages = [];

				for ( var pageNumber = 1; pageNumber <= pdfDoc.numPages; ++pageNumber ) {

					if ( pageNumberOnly !== undefined && pageNumberOnly.indexOf(pageNumber) === -1 )
						continue;

					allPages.push(
						pdfDoc.getPage(pageNumber)
						.then(function(page) {

							var viewport = page.getViewport({ scale: 1 });

							var printCanvasElt = printContainerElement.appendChild(win.document.createElement('canvas'));
							printCanvasElt.setAttribute('id', 'print-canvas')
							printCanvasElt.width = (viewport.width * PRINT_UNITS);
							printCanvasElt.height = (viewport.height * PRINT_UNITS);

							return page.render({
								canvasContext: printCanvasElt.getContext('2d'),
								transform: [ // Additional transform, applied just before viewport transform.
									PRINT_UNITS, 0, 0,
									PRINT_UNITS, 0, 0
								],
								viewport: viewport,
								intent: 'print'
							}).promise;
						})
					);
				}

				Promise.all(allPages)
				.then(function() {

					win.focus(); // Required for IE
					if (win.document.queryCommandSupported('print')) {
						win.document.execCommand('print', false, null);
					} else {
						win.print();
					}
					removePrintContainer();
				})
				.catch(function(err) {
					removePrintContainer();
					emitEvent('error', err);
				})
			})
		}

		this.renderPage = function(rotate) {
			if ( pdfRender !== null ) {

				if ( canceling )
					return;
				canceling = true;
				pdfRender.cancel().catch(function(err) {
					emitEvent('error', err);
				});
				return;
			}

			if ( pdfPage === null )
				return;

			var pageRotate = (pdfPage.rotate === undefined ? 0 : pdfPage.rotate) + (rotate === undefined ? 0 : rotate);

			var scale = canvasElt.offsetWidth / pdfPage.getViewport({ scale: 1 }).width * (window.devicePixelRatio || 1);
			var viewport = pdfPage.getViewport({ scale: scale, rotation:pageRotate });

			emitEvent('page-size', viewport.width, viewport.height, scale);

			canvasElt.width = viewport.width;
			canvasElt.height = viewport.height;

			pdfRender = pdfPage.render({
				canvasContext: canvasElt.getContext('2d'),
				viewport: viewport
			});

			annotationLayerElt.style.visibility = 'hidden';
			clearAnnotations();

			var viewer = {
				scrollPageIntoView: function(params) {
					emitEvent('link-clicked', params.pageNumber)
				},
			};

			var linkService = new PDFLinkService();
			linkService.setDocument(pdfDoc);
			linkService.setViewer(viewer);

			pendingOperation = pendingOperation.then(function() {

				var getAnnotationsOperation =
				pdfPage.getAnnotations({ intent: 'display' })
				.then(function(annotations) {

					PDFJS.AnnotationLayer.render({
						viewport: viewport.clone({ dontFlip: true }),
						div: annotationLayerElt,
						annotations: annotations,
						page: pdfPage,
						linkService: linkService,
						renderInteractiveForms: false
					});
				});

				var pdfRenderOperation =
				pdfRender.promise
				.then(function() {

					annotationLayerElt.style.visibility = '';
					canceling = false;
					pdfRender = null;
				})
				.catch(function(err) {

					pdfRender = null;
					if ( err instanceof PDFJS.RenderingCancelledException ) {

						canceling = false;
						this.renderPage(rotate);
						return;
					}
					emitEvent('error', err);
				}.bind(this))

				return Promise.all([getAnnotationsOperation, pdfRenderOperation]);
			}.bind(this));
		}


		this.forEachPage = function(pageCallback) {

			var numPages = pdfDoc.numPages;

			(function next(pageNum) {

				pdfDoc.getPage(pageNum)
				.then(pageCallback)
				.then(function() {

					if ( ++pageNum <= numPages )
						next(pageNum);
				})
			})(1);
		}


		this.loadPage = function(pageNumber, rotate) {

			pdfPage = null;

			if ( pdfDoc === null )
				return;

			pendingOperation = pendingOperation.then(function() {

				return pdfDoc.getPage(pageNumber);
			})
			.then(function(page) {

				pdfPage = page;
				this.renderPage(rotate);
				emitEvent('page-loaded', page.pageNumber);
			}.bind(this))
			.catch(function(err) {

				clearCanvas();
				clearAnnotations();
				emitEvent('error', err);
			});
		}

		this.loadDocument = function(src) {

			pdfDoc = null;
			pdfPage = null;

			emitEvent('num-pages', undefined);

			if ( !src ) {

				canvasElt.removeAttribute('width');
				canvasElt.removeAttribute('height');
				clearAnnotations();
				return;
			}

			// wait for pending operation ends
			pendingOperation = pendingOperation.then(function() {

				var loadingTask;
				if ( isPDFDocumentLoadingTask(src) ) {

					if ( src.destroyed ) {

						emitEvent('error', new Error('loadingTask has been destroyed'));
						return
					}

					loadingTask = src;
				} else {

					loadingTask = createLoadingTask(src, {
						onPassword: function(updatePassword, reason) {

							var reasonStr;
							switch (reason) {
								case PDFJS.PasswordResponses.NEED_PASSWORD:
									reasonStr = 'NEED_PASSWORD';
									break;
								case PDFJS.PasswordResponses.INCORRECT_PASSWORD:
									reasonStr = 'INCORRECT_PASSWORD';
									break;
							}
							emitEvent('password', updatePassword, reasonStr);
						},
						onProgress: function(status) {

							var ratio = status.loaded / status.total;
							emitEvent('progress', Math.min(ratio, 1));
						}
					});
				}

				return loadingTask.promise;
			})
			.then(function(pdf) {

				pdfDoc = pdf;
				emitEvent('num-pages', pdf.numPages);
				emitEvent('loaded');
			})
			.catch(function(err) {

				clearCanvas();
				clearAnnotations();
				emitEvent('error', err);
			})
		}

		annotationLayerElt.style.transformOrigin = '0 0';
	}

	return {
		createLoadingTask: createLoadingTask,
		PDFJSWrapper: PDFJSWrapper,
	}
}

Keywords: Javascript Front-end Vue.js utils

Added by paruby on Wed, 12 Jan 2022 05:10:44 +0200