pdf-js-wrapper.js 5.8 KB

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