pdf-js-wrapper.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // NOTE: using "legacy" build as main build requires webpack v5
  2. // import PDFJS from 'pdfjs-dist/webpack'
  3. import * as PDFJS from 'pdfjs-dist/legacy/build/pdf'
  4. import * as PDFJSViewer from 'pdfjs-dist/legacy/web/pdf_viewer'
  5. import PDFJSWorker from 'pdfjs-dist/legacy/build/pdf.worker'
  6. import 'pdfjs-dist/legacy/web/pdf_viewer.css'
  7. import getMeta from '../../../utils/meta'
  8. import { captureMessage } from '../../../infrastructure/error-reporter'
  9. if (typeof window !== 'undefined' && 'Worker' in window) {
  10. PDFJS.GlobalWorkerOptions.workerPort = new PDFJSWorker()
  11. }
  12. const params = new URLSearchParams(window.location.search)
  13. const disableFontFace = params.get('disable-font-face') === 'true'
  14. const cMapUrl = getMeta('ol-pdfCMapsPath')
  15. const imageResourcesPath = getMeta('ol-pdfImageResourcesPath')
  16. const disableStream = process.env.NODE_ENV !== 'test'
  17. const rangeChunkSize = 128 * 1024 // 128K chunks
  18. export default class PDFJSWrapper {
  19. constructor(container) {
  20. this.container = container
  21. // create the event bus
  22. const eventBus = new PDFJSViewer.EventBus()
  23. // create the link service
  24. const linkService = new PDFJSViewer.PDFLinkService({
  25. eventBus,
  26. externalLinkTarget: 2,
  27. externalLinkRel: 'noopener',
  28. })
  29. // create the localization
  30. // const l10n = new PDFJSViewer.GenericL10n('en-GB') // TODO: locale mapping?
  31. // create the viewer
  32. const viewer = new PDFJSViewer.PDFViewer({
  33. container,
  34. eventBus,
  35. imageResourcesPath,
  36. linkService,
  37. // l10n, // commented out since it currently breaks `aria-label` rendering in pdf pages
  38. enableScripting: false, // default is false, but set explicitly to be sure
  39. enableXfa: false, // default is false (2021-10-12), but set explicitly to be sure
  40. renderInteractiveForms: false,
  41. })
  42. linkService.setViewer(viewer)
  43. this.eventBus = eventBus
  44. this.linkService = linkService
  45. this.viewer = viewer
  46. }
  47. // load a document from a URL
  48. loadDocument(url) {
  49. // cancel any previous loading task
  50. if (this.loadDocumentTask) {
  51. this.loadDocumentTask.destroy()
  52. this.loadDocumentTask = undefined
  53. }
  54. return new Promise((resolve, reject) => {
  55. this.loadDocumentTask = PDFJS.getDocument({
  56. url,
  57. cMapUrl,
  58. cMapPacked: true,
  59. disableFontFace,
  60. rangeChunkSize,
  61. disableAutoFetch: true,
  62. disableStream,
  63. textLayerMode: 2, // PDFJSViewer.TextLayerMode.ENABLE,
  64. })
  65. this.loadDocumentTask.promise
  66. .then(doc => {
  67. if (!this.loadDocumentTask) {
  68. return // ignoring the response since loading task has been aborted
  69. }
  70. const previousDoc = this.viewer.pdfDocument
  71. this.viewer.setDocument(doc)
  72. this.linkService.setDocument(doc)
  73. resolve(doc)
  74. if (previousDoc) {
  75. previousDoc.cleanup().catch(console.error)
  76. previousDoc.destroy()
  77. }
  78. })
  79. .catch(error => {
  80. if (this.loadDocumentTask) {
  81. if (error.name !== 'MissingPDFException') {
  82. captureMessage(`pdf preview error ${error}`)
  83. }
  84. reject(error)
  85. }
  86. })
  87. .finally(() => {
  88. this.loadDocumentTask = undefined
  89. })
  90. })
  91. }
  92. // update the current scale value if the container size changes
  93. updateOnResize() {
  94. const currentScaleValue = this.viewer.currentScaleValue
  95. if (
  96. currentScaleValue === 'auto' ||
  97. currentScaleValue === 'page-fit' ||
  98. currentScaleValue === 'page-width'
  99. ) {
  100. this.viewer.currentScaleValue = currentScaleValue
  101. }
  102. this.viewer.update()
  103. }
  104. // get the page and offset of a click event
  105. clickPosition(event, pageElement, textLayer) {
  106. const { viewport } = this.viewer.getPageView(textLayer.pageNumber - 1)
  107. const pageRect = pageElement.querySelector('canvas').getBoundingClientRect()
  108. const dx = event.clientX - pageRect.left
  109. const dy = event.clientY - pageRect.top
  110. const [left, top] = viewport.convertToPdfPoint(dx, dy)
  111. return {
  112. page: textLayer.pageNumber - 1,
  113. offset: {
  114. left,
  115. top: viewport.viewBox[3] - top,
  116. },
  117. }
  118. }
  119. // get the current page, offset and page size
  120. get currentPosition() {
  121. const pageIndex = this.viewer.currentPageNumber - 1
  122. const pageView = this.viewer.getPageView(pageIndex)
  123. const pageRect = pageView.div.getBoundingClientRect()
  124. const containerRect = this.container.getBoundingClientRect()
  125. const dy = containerRect.top - pageRect.top
  126. const [, , width, height] = pageView.viewport.viewBox
  127. const [, top] = pageView.viewport.convertToPdfPoint(0, dy)
  128. return {
  129. page: pageIndex,
  130. offset: { top, left: 0 },
  131. pageSize: { height, width },
  132. }
  133. }
  134. set currentPosition(position) {
  135. const destArray = [
  136. null,
  137. {
  138. name: 'XYZ', // 'XYZ' = scroll to the given coordinates
  139. },
  140. position.offset.left,
  141. position.offset.top,
  142. null,
  143. ]
  144. this.viewer.scrollPageIntoView({
  145. pageNumber: position.page + 1,
  146. destArray,
  147. })
  148. }
  149. abortDocumentLoading() {
  150. this.loadDocumentTask = undefined
  151. }
  152. destroy() {
  153. if (this.loadDocumentTask) {
  154. this.loadDocumentTask.destroy()
  155. this.loadDocumentTask = undefined
  156. }
  157. this.viewer.destroy()
  158. }
  159. }