| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- import { captureException } from '@/infrastructure/error-reporter'
- import { generatePdfCachingTransportFactory } from './pdf-caching-transport'
- import { debugConsole } from '@/utils/debugging'
- import { PDFJS, loadPdfDocumentFromUrl, imageResourcesPath } from './pdf-js'
- import {
- PDFViewer,
- EventBus,
- PDFLinkService,
- LinkTarget,
- } from 'pdfjs-dist/legacy/web/pdf_viewer.mjs'
- import 'pdfjs-dist/legacy/web/pdf_viewer.css'
- const DEFAULT_RANGE_CHUNK_SIZE = 128 * 1024 // 128K chunks
- export default class PDFJSWrapper {
- private loadDocumentTask: PDFJS.PDFDocumentLoadingTask | undefined
- public readonly viewer: PDFViewer
- public readonly eventBus: EventBus
- private readonly linkService: PDFLinkService
- // eslint-disable-next-line no-useless-constructor
- constructor(public container: HTMLDivElement) {
- // create the event bus
- this.eventBus = new EventBus()
- // create the link service
- this.linkService = new PDFLinkService({
- eventBus: this.eventBus,
- externalLinkTarget: LinkTarget.BLANK,
- externalLinkRel: 'noopener',
- })
- // create the viewer
- this.viewer = new PDFViewer({
- container: this.container,
- eventBus: this.eventBus,
- imageResourcesPath,
- linkService: this.linkService,
- maxCanvasPixels: 8192 * 8192, // default is 4096 * 4096, increased for better resolution at high zoom levels
- annotationMode: PDFJS.AnnotationMode.ENABLE, // enable annotations but not forms
- annotationEditorMode: PDFJS.AnnotationEditorType.DISABLE, // disable annotation editing
- })
- this.linkService.setViewer(this.viewer)
- }
- // load a document from a URL
- loadDocument({
- url,
- pdfFile,
- abortController,
- handleFetchError,
- }: {
- url: string
- pdfFile: Record<string, any>
- abortController: AbortController
- handleFetchError: (error: Error) => void
- }) {
- // cancel any previous loading task
- if (this.loadDocumentTask) {
- this.loadDocumentTask.destroy().catch(debugConsole.error)
- this.loadDocumentTask = undefined
- }
- return new Promise<PDFJS.PDFDocumentProxy>((resolve, reject) => {
- const rangeTransport = generatePdfCachingTransportFactory()({
- url,
- pdfFile,
- abortController,
- handleFetchError,
- })
- let rangeChunkSize = DEFAULT_RANGE_CHUNK_SIZE
- if (rangeTransport && pdfFile.size < 2 * DEFAULT_RANGE_CHUNK_SIZE) {
- // pdf.js disables the "bulk" download optimization when providing a
- // custom range transport. Restore it by bumping the chunk size.
- rangeChunkSize = pdfFile.size
- }
- this.loadDocumentTask = loadPdfDocumentFromUrl(url, {
- rangeChunkSize,
- range: rangeTransport,
- })
- this.loadDocumentTask.promise
- .then(doc => {
- if (!this.loadDocumentTask || !this.viewer) {
- return // ignoring the response since loading task has been aborted
- }
- const previousDoc = this.viewer.pdfDocument
- this.viewer.setDocument(doc)
- this.linkService.setDocument(doc)
- resolve(doc)
- if (previousDoc) {
- previousDoc.cleanup().catch(debugConsole.error)
- previousDoc.destroy().catch(debugConsole.error)
- }
- })
- .catch(error => {
- if (this.loadDocumentTask) {
- if (!error || error.name !== 'MissingPDFException') {
- captureException(error, {
- tags: { handler: 'pdf-preview' },
- })
- }
- reject(error)
- }
- })
- .finally(() => {
- this.loadDocumentTask = undefined
- })
- })
- }
- // update the current scale value if the container size changes
- updateOnResize() {
- if (!this.isVisible()) {
- return
- }
- // Use requestAnimationFrame to prevent errors like "ResizeObserver loop
- // completed with undelivered notifications" that can occur if updating the
- // viewer causes another repaint. The cost of this is that the viewer update
- // lags one frame behind, but it's unlikely to matter.
- // Further reading: https://github.com/WICG/resize-observer/issues/38
- window.requestAnimationFrame(() => {
- const currentScaleValue = this.viewer.currentScaleValue
- if (
- currentScaleValue === 'auto' ||
- currentScaleValue === 'page-fit' ||
- currentScaleValue === 'page-height' ||
- currentScaleValue === 'page-width'
- ) {
- this.viewer.currentScaleValue = currentScaleValue
- }
- this.viewer.update()
- })
- }
- // get the page and offset of a click event
- clickPosition(event: MouseEvent, canvas: HTMLCanvasElement, page: number) {
- if (!canvas) {
- return
- }
- const { viewport } = this.viewer.getPageView(page)
- const pageRect = canvas.getBoundingClientRect()
- const dx = event.clientX - pageRect.left
- const dy = event.clientY - pageRect.top
- const [left, top] = viewport.convertToPdfPoint(dx, dy)
- return {
- page,
- offset: {
- left,
- top: viewport.viewBox[3] - top,
- },
- }
- }
- // get the current page, offset and page size
- get currentPosition() {
- const pageIndex = this.viewer.currentPageNumber - 1
- const pageView = this.viewer.getPageView(pageIndex)
- const pageRect = pageView.div.getBoundingClientRect()
- const containerRect = this.container.getBoundingClientRect()
- const dy = containerRect.top - pageRect.top
- const dx = containerRect.left - pageRect.left
- const [left, top] = pageView.viewport.convertToPdfPoint(dx, dy)
- const [, , width, height] = pageView.viewport.viewBox
- return {
- page: pageIndex,
- offset: { top, left },
- pageSize: { height, width },
- }
- }
- scrollToPosition(position: Record<string, any>, scale = null) {
- const destArray = [
- null,
- {
- name: 'XYZ', // 'XYZ' = scroll to the given coordinates
- },
- position.offset.left,
- position.offset.top,
- scale,
- ]
- this.viewer.scrollPageIntoView({
- pageNumber: position.page + 1,
- destArray,
- })
- // scroll the page left and down by an extra few pixels to account for the pdf.js viewer page border
- const pageIndex = this.viewer.currentPageNumber - 1
- const pageView = this.viewer.getPageView(pageIndex)
- const offset = parseFloat(getComputedStyle(pageView.div).borderWidth)
- this.viewer.container.scrollBy({
- top: -offset,
- left: -offset,
- })
- }
- isVisible() {
- return this.viewer.container.offsetParent !== null
- }
- abortDocumentLoading() {
- this.loadDocumentTask = undefined
- }
- async destroy() {
- await this.loadDocumentTask?.destroy().catch(debugConsole.error)
- this.loadDocumentTask = undefined
- await this.viewer.pdfDocument?.destroy().catch(debugConsole.error)
- }
- }
|