mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Convert PDF.js code to TypeScript
GitOrigin-RevId: 840bc48816d78497131b927522b4f6f7940bb0c4
This commit is contained in:
parent
be45aec5b5
commit
df6147bb4d
11 changed files with 135 additions and 164 deletions
|
@ -15,9 +15,9 @@ const FileViewPdf: FC<{
|
||||||
const handleContainer = useCallback(
|
const handleContainer = useCallback(
|
||||||
async (element: HTMLDivElement | null) => {
|
async (element: HTMLDivElement | null) => {
|
||||||
if (element) {
|
if (element) {
|
||||||
const { PDFJS } = await import(
|
const { loadPdfDocumentFromUrl } = await import(
|
||||||
'../../pdf-preview/util/pdf-js-versions'
|
'@/features/pdf-preview/util/pdf-js'
|
||||||
).then(m => m.default as any)
|
)
|
||||||
|
|
||||||
// bail out if loading PDF.js took too long
|
// bail out if loading PDF.js took too long
|
||||||
if (!mountedRef.current) {
|
if (!mountedRef.current) {
|
||||||
|
@ -33,10 +33,7 @@ const FileViewPdf: FC<{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdf = await PDFJS.getDocument({
|
const pdf = await loadPdfDocumentFromUrl(preview.url).promise
|
||||||
url: preview.url,
|
|
||||||
isEvalSupported: false,
|
|
||||||
}).promise
|
|
||||||
|
|
||||||
// bail out if loading the PDF took too long
|
// bail out if loading the PDF took too long
|
||||||
if (!mountedRef.current) {
|
if (!mountedRef.current) {
|
||||||
|
@ -61,7 +58,7 @@ const FileViewPdf: FC<{
|
||||||
|
|
||||||
element.append(canvas)
|
element.append(canvas)
|
||||||
page.render({
|
page.render({
|
||||||
canvasContext: canvas.getContext('2d'),
|
canvasContext: canvas.getContext('2d')!,
|
||||||
viewport,
|
viewport,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
memo,
|
|
||||||
MouseEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { debounce, throttle } from 'lodash'
|
import { debounce, throttle } from 'lodash'
|
||||||
import PdfViewerControlsToolbar from './pdf-viewer-controls-toolbar'
|
import PdfViewerControlsToolbar from './pdf-viewer-controls-toolbar'
|
||||||
import { useProjectContext } from '../../../shared/context/project-context'
|
import { useProjectContext } from '../../../shared/context/project-context'
|
||||||
|
@ -22,6 +15,7 @@ import { debugConsole } from '@/utils/debugging'
|
||||||
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
|
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
|
||||||
import usePresentationMode from '../hooks/use-presentation-mode'
|
import usePresentationMode from '../hooks/use-presentation-mode'
|
||||||
import useMouseWheelZoom from '../hooks/use-mouse-wheel-zoom'
|
import useMouseWheelZoom from '../hooks/use-mouse-wheel-zoom'
|
||||||
|
import { PDFJS } from '../util/pdf-js'
|
||||||
|
|
||||||
type PdfJsViewerProps = {
|
type PdfJsViewerProps = {
|
||||||
url: string
|
url: string
|
||||||
|
@ -70,20 +64,18 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
||||||
const handleContainer = useCallback(
|
const handleContainer = useCallback(
|
||||||
parent => {
|
parent => {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const wrapper = new PDFJSWrapper(parent.firstChild)
|
let wrapper: PDFJSWrapper | undefined
|
||||||
wrapper
|
try {
|
||||||
.init()
|
wrapper = new PDFJSWrapper(parent.firstChild)
|
||||||
.then(() => {
|
|
||||||
setPdfJsWrapper(wrapper)
|
setPdfJsWrapper(wrapper)
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
|
||||||
setLoadingError(true)
|
setLoadingError(true)
|
||||||
captureException(error)
|
captureException(error)
|
||||||
})
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setPdfJsWrapper(null)
|
setPdfJsWrapper(null)
|
||||||
wrapper.destroy()
|
wrapper?.destroy().catch(debugConsole.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -180,7 +172,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
||||||
const handleFetchError = (err: Error) => {
|
const handleFetchError = (err: Error) => {
|
||||||
if (abortController.signal.aborted) return
|
if (abortController.signal.aborted) return
|
||||||
// The error is already logged at the call-site with additional context.
|
// The error is already logged at the call-site with additional context.
|
||||||
if (err instanceof pdfJsWrapper.PDFJS.MissingPDFException) {
|
if (err instanceof PDFJS.MissingPDFException) {
|
||||||
setError('rendering-error-expected')
|
setError('rendering-error-expected')
|
||||||
} else {
|
} else {
|
||||||
setError('rendering-error')
|
setError('rendering-error')
|
||||||
|
@ -254,7 +246,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
||||||
if (!textLayerDiv.dataset.listeningForDoubleClick) {
|
if (!textLayerDiv.dataset.listeningForDoubleClick) {
|
||||||
textLayerDiv.dataset.listeningForDoubleClick = true
|
textLayerDiv.dataset.listeningForDoubleClick = true
|
||||||
|
|
||||||
const doubleClickListener: MouseEventHandler = event => {
|
const doubleClickListener = (event: MouseEvent) => {
|
||||||
const clickPosition = pdfJsWrapper.clickPosition(
|
const clickPosition = pdfJsWrapper.clickPosition(
|
||||||
event,
|
event,
|
||||||
textLayerDiv.closest('.page').querySelector('canvas'),
|
textLayerDiv.closest('.page').querySelector('canvas'),
|
||||||
|
@ -355,7 +347,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
||||||
|
|
||||||
for (const highlight of highlights) {
|
for (const highlight of highlights) {
|
||||||
try {
|
try {
|
||||||
const element = buildHighlightElement(highlight, pdfJsWrapper)
|
const element = buildHighlightElement(highlight, pdfJsWrapper.viewer)
|
||||||
elements.push(element)
|
elements.push(element)
|
||||||
intersectionObserver.observe(element)
|
intersectionObserver.observe(element)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { memo, Suspense } from 'react'
|
import { ElementType, memo, Suspense } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import PdfLogsViewer from './pdf-logs-viewer'
|
import PdfLogsViewer from './pdf-logs-viewer'
|
||||||
import PdfViewer from './pdf-viewer'
|
import PdfViewer from './pdf-viewer'
|
||||||
|
@ -10,7 +10,10 @@ import CompileTimeWarningUpgradePrompt from './compile-time-warning-upgrade-prom
|
||||||
import { PdfPreviewProvider } from './pdf-preview-provider'
|
import { PdfPreviewProvider } from './pdf-preview-provider'
|
||||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||||
|
|
||||||
const pdfPreviewPromotions = importOverleafModules('pdfPreviewPromotions')
|
const pdfPreviewPromotions = importOverleafModules('pdfPreviewPromotions') as {
|
||||||
|
import: { default: ElementType }
|
||||||
|
path: string
|
||||||
|
}[]
|
||||||
|
|
||||||
function PdfPreviewPane() {
|
function PdfPreviewPane() {
|
||||||
const { pdfUrl, hasShortCompileTimeout } = useCompileContext()
|
const { pdfUrl, hasShortCompileTimeout } = useCompileContext()
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import PDFJSWrapper from '../util/pdf-js-wrapper'
|
import PDFJSWrapper from '../util/pdf-js-wrapper'
|
||||||
import { sendMB } from '@/infrastructure/event-tracking'
|
import { sendMB } from '@/infrastructure/event-tracking'
|
||||||
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
|
||||||
type StoredPDFState = {
|
type StoredPDFState = {
|
||||||
scrollMode?: number
|
scrollMode?: number
|
||||||
|
@ -116,7 +117,9 @@ export default function usePresentationMode(
|
||||||
sendMB('pdf-viewer-enter-presentation-mode')
|
sendMB('pdf-viewer-enter-presentation-mode')
|
||||||
|
|
||||||
if (pdfJsWrapper) {
|
if (pdfJsWrapper) {
|
||||||
pdfJsWrapper.container.parentNode.requestFullscreen()
|
pdfJsWrapper.container.parentElement
|
||||||
|
?.requestFullscreen()
|
||||||
|
.catch(debugConsole.error)
|
||||||
}
|
}
|
||||||
}, [pdfJsWrapper])
|
}, [pdfJsWrapper])
|
||||||
|
|
||||||
|
@ -136,8 +139,8 @@ export default function usePresentationMode(
|
||||||
|
|
||||||
const handleExitFullscreen = useCallback(() => {
|
const handleExitFullscreen = useCallback(() => {
|
||||||
if (pdfJsWrapper) {
|
if (pdfJsWrapper) {
|
||||||
pdfJsWrapper.viewer.scrollMode = storedState.current.scrollMode
|
pdfJsWrapper.viewer.scrollMode = storedState.current.scrollMode!
|
||||||
pdfJsWrapper.viewer.spreadMode = storedState.current.spreadMode
|
pdfJsWrapper.viewer.spreadMode = storedState.current.spreadMode!
|
||||||
|
|
||||||
if (storedState.current.currentScaleValue !== undefined) {
|
if (storedState.current.currentScaleValue !== undefined) {
|
||||||
setScale(storedState.current.currentScaleValue)
|
setScale(storedState.current.currentScaleValue)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export function buildHighlightElement(highlight, wrapper) {
|
import { PDFJS } from '@/features/pdf-preview/util/pdf-js'
|
||||||
const pageView = wrapper.viewer.getPageView(highlight.page - 1)
|
|
||||||
|
export function buildHighlightElement(highlight, viewer) {
|
||||||
|
const pageView = viewer.getPageView(highlight.page - 1)
|
||||||
|
|
||||||
const viewport = pageView.viewport
|
const viewport = pageView.viewport
|
||||||
|
|
||||||
|
@ -12,7 +14,7 @@ export function buildHighlightElement(highlight, wrapper) {
|
||||||
height - highlight.v + 10, // yMax
|
height - highlight.v + 10, // yMax
|
||||||
])
|
])
|
||||||
|
|
||||||
const [left, top, right, bottom] = wrapper.PDFJS.Util.normalizeRect(rect)
|
const [left, top, right, bottom] = PDFJS.Util.normalizeRect(rect)
|
||||||
|
|
||||||
const element = document.createElement('div')
|
const element = document.createElement('div')
|
||||||
element.style.left = Math.floor(pageView.div.offsetLeft + left) + 'px'
|
element.style.left = Math.floor(pageView.div.offsetLeft + left) + 'px'
|
||||||
|
@ -27,7 +29,7 @@ export function buildHighlightElement(highlight, wrapper) {
|
||||||
element.style.opacity = '0'
|
element.style.opacity = '0'
|
||||||
element.style.transition = 'opacity 1s'
|
element.style.transition = 'opacity 1s'
|
||||||
|
|
||||||
wrapper.viewer.viewer.append(element)
|
viewer.viewer?.append(element)
|
||||||
|
|
||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import OError from '@overleaf/o-error'
|
import OError from '@overleaf/o-error'
|
||||||
import { fallbackRequest, fetchRange } from './pdf-caching'
|
import { fallbackRequest, fetchRange } from './pdf-caching'
|
||||||
import { captureException } from '../../../infrastructure/error-reporter'
|
import { captureException } from '@/infrastructure/error-reporter'
|
||||||
import { getPdfCachingMetrics } from './metrics'
|
import { getPdfCachingMetrics } from './metrics'
|
||||||
import {
|
import {
|
||||||
cachedUrlLookupEnabled,
|
cachedUrlLookupEnabled,
|
||||||
|
@ -9,16 +9,17 @@ import {
|
||||||
prefetchLargeEnabled,
|
prefetchLargeEnabled,
|
||||||
trackPdfDownloadEnabled,
|
trackPdfDownloadEnabled,
|
||||||
} from './pdf-caching-flags'
|
} from './pdf-caching-flags'
|
||||||
import { isNetworkError } from '../../../utils/isNetworkError'
|
import { isNetworkError } from '@/utils/isNetworkError'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { PDFJS } from './pdf-js'
|
||||||
|
|
||||||
// 30 seconds: The shutdown grace period of a clsi pre-emp instance.
|
// 30 seconds: The shutdown grace period of a clsi pre-emp instance.
|
||||||
const STALE_OUTPUT_REQUEST_THRESHOLD_MS = 30 * 1000
|
const STALE_OUTPUT_REQUEST_THRESHOLD_MS = 30 * 1000
|
||||||
|
|
||||||
export function generatePdfCachingTransportFactory(PDFJS) {
|
export function generatePdfCachingTransportFactory() {
|
||||||
// NOTE: The custom transport can be used for tracking download volume.
|
// NOTE: The custom transport can be used for tracking download volume.
|
||||||
if (!enablePdfCaching && !trackPdfDownloadEnabled) {
|
if (!enablePdfCaching && !trackPdfDownloadEnabled) {
|
||||||
return () => null
|
return () => undefined
|
||||||
}
|
}
|
||||||
const usageScore = new Map()
|
const usageScore = new Map()
|
||||||
const cachedUrls = new Map()
|
const cachedUrls = new Map()
|
||||||
|
@ -180,7 +181,7 @@ export function generatePdfCachingTransportFactory(PDFJS) {
|
||||||
if (metrics.failedOnce) {
|
if (metrics.failedOnce) {
|
||||||
// Disable pdf caching once any fetch request failed.
|
// Disable pdf caching once any fetch request failed.
|
||||||
// Be trigger-happy here until we reached a stable state of the feature.
|
// Be trigger-happy here until we reached a stable state of the feature.
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
// Latency is collected per preview cycle.
|
// Latency is collected per preview cycle.
|
||||||
metrics.latencyComputeMax = 0
|
metrics.latencyComputeMax = 0
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
// To add a new version, copy and adjust one of the `importPDFJS*` functions below,
|
|
||||||
// add the variant to the "switch" statement, and add to `pdfjsVersions` in webpack.config.js
|
|
||||||
|
|
||||||
import 'core-js/stable/global-this' // polyfill for globalThis (used by pdf.js)
|
|
||||||
import 'core-js/stable/promise/all-settled' // polyfill for Promise.allSettled (used by pdf.js)
|
|
||||||
import 'core-js/stable/structured-clone' // polyfill for global.StructuredClone (used by pdf.js)
|
|
||||||
import 'core-js/stable/array/at' // polyfill for Array.prototype.at (used by pdf.js)
|
|
||||||
import { createWorker } from '@/utils/worker'
|
|
||||||
|
|
||||||
async function importPDFJS() {
|
|
||||||
const cMapUrl = '/js/pdfjs-dist/cmaps/'
|
|
||||||
const standardFontDataUrl = '/fonts/pdfjs-dist/'
|
|
||||||
const imageResourcesPath = '/images/pdfjs-dist/'
|
|
||||||
|
|
||||||
// ensure that PDF.js is loaded before importing the viewer
|
|
||||||
const PDFJS = await import('pdfjs-dist/legacy/build/pdf')
|
|
||||||
|
|
||||||
const [PDFJSViewer] = await Promise.all([
|
|
||||||
import('pdfjs-dist/legacy/web/pdf_viewer'),
|
|
||||||
import('pdfjs-dist/legacy/web/pdf_viewer.css'),
|
|
||||||
])
|
|
||||||
|
|
||||||
createWorker(() => {
|
|
||||||
PDFJS.GlobalWorkerOptions.workerPort = new Worker(
|
|
||||||
new URL('pdfjs-dist/legacy/build/pdf.worker.mjs', import.meta.url) // NOTE: .mjs extension
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
PDFJS,
|
|
||||||
PDFJSViewer,
|
|
||||||
cMapUrl,
|
|
||||||
imageResourcesPath,
|
|
||||||
standardFontDataUrl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default importPDFJS()
|
|
|
@ -1,81 +1,70 @@
|
||||||
import { captureException } from '../../../infrastructure/error-reporter'
|
import { captureException } from '@/infrastructure/error-reporter'
|
||||||
import { generatePdfCachingTransportFactory } from './pdf-caching-transport'
|
import { generatePdfCachingTransportFactory } from './pdf-caching-transport'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { PDFJS, loadPdfDocumentFromUrl, imageResourcesPath } from './pdf-js'
|
||||||
const params = new URLSearchParams(window.location.search)
|
import {
|
||||||
const disableFontFace = params.get('disable-font-face') === 'true'
|
PDFViewer,
|
||||||
const disableStream = process.env.NODE_ENV !== 'test'
|
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
|
const DEFAULT_RANGE_CHUNK_SIZE = 128 * 1024 // 128K chunks
|
||||||
|
|
||||||
export default class PDFJSWrapper {
|
export default class PDFJSWrapper {
|
||||||
constructor(container) {
|
private loadDocumentTask: PDFJS.PDFDocumentLoadingTask | undefined
|
||||||
this.container = container
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
public readonly viewer: PDFViewer
|
||||||
const {
|
public readonly eventBus: EventBus
|
||||||
PDFJS,
|
private readonly linkService: PDFLinkService
|
||||||
PDFJSViewer,
|
|
||||||
cMapUrl,
|
|
||||||
imageResourcesPath,
|
|
||||||
standardFontDataUrl,
|
|
||||||
} = await import('./pdf-js-versions').then(m => {
|
|
||||||
return m.default
|
|
||||||
})
|
|
||||||
|
|
||||||
this.PDFJS = PDFJS
|
|
||||||
this.genPdfCachingTransport = generatePdfCachingTransportFactory(PDFJS)
|
|
||||||
this.PDFJSViewer = PDFJSViewer
|
|
||||||
this.cMapUrl = cMapUrl
|
|
||||||
this.standardFontDataUrl = standardFontDataUrl
|
|
||||||
this.imageResourcesPath = imageResourcesPath
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-useless-constructor
|
||||||
|
constructor(public container: HTMLDivElement) {
|
||||||
// create the event bus
|
// create the event bus
|
||||||
const eventBus = new PDFJSViewer.EventBus()
|
this.eventBus = new EventBus()
|
||||||
|
|
||||||
// create the link service
|
// create the link service
|
||||||
const linkService = new PDFJSViewer.PDFLinkService({
|
this.linkService = new PDFLinkService({
|
||||||
eventBus,
|
eventBus: this.eventBus,
|
||||||
externalLinkTarget: 2,
|
externalLinkTarget: LinkTarget.BLANK,
|
||||||
externalLinkRel: 'noopener',
|
externalLinkRel: 'noopener',
|
||||||
})
|
})
|
||||||
|
|
||||||
// create the localization
|
|
||||||
// const l10n = new PDFJSViewer.GenericL10n('en-GB') // TODO: locale mapping?
|
|
||||||
|
|
||||||
// create the viewer
|
// create the viewer
|
||||||
const viewer = new PDFJSViewer.PDFViewer({
|
this.viewer = new PDFViewer({
|
||||||
container: this.container,
|
container: this.container,
|
||||||
eventBus,
|
eventBus: this.eventBus,
|
||||||
imageResourcesPath,
|
imageResourcesPath,
|
||||||
linkService,
|
linkService: this.linkService,
|
||||||
// l10n, // commented out since it currently breaks `aria-label` rendering in pdf pages
|
|
||||||
enableScripting: false, // default is false, but set explicitly to be sure
|
|
||||||
enableXfa: false, // default is false (2021-10-12), but set explicitly to be sure
|
|
||||||
renderInteractiveForms: false,
|
|
||||||
maxCanvasPixels: 8192 * 8192, // default is 4096 * 4096, increased for better resolution at high zoom levels
|
maxCanvasPixels: 8192 * 8192, // default is 4096 * 4096, increased for better resolution at high zoom levels
|
||||||
annotationMode: PDFJS.AnnotationMode?.ENABLE, // enable annotations but not forms
|
annotationMode: PDFJS.AnnotationMode.ENABLE, // enable annotations but not forms
|
||||||
annotationEditorMode: PDFJS.AnnotationEditorType?.DISABLE, // disable annotation editing
|
annotationEditorMode: PDFJS.AnnotationEditorType.DISABLE, // disable annotation editing
|
||||||
})
|
})
|
||||||
|
|
||||||
linkService.setViewer(viewer)
|
this.linkService.setViewer(this.viewer)
|
||||||
|
|
||||||
this.eventBus = eventBus
|
|
||||||
this.linkService = linkService
|
|
||||||
this.viewer = viewer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load a document from a URL
|
// load a document from a URL
|
||||||
loadDocument({ url, pdfFile, abortController, handleFetchError }) {
|
loadDocument({
|
||||||
|
url,
|
||||||
|
pdfFile,
|
||||||
|
abortController,
|
||||||
|
handleFetchError,
|
||||||
|
}: {
|
||||||
|
url: string
|
||||||
|
pdfFile: Record<string, any>
|
||||||
|
abortController: AbortController
|
||||||
|
handleFetchError: (error: Error) => void
|
||||||
|
}) {
|
||||||
// cancel any previous loading task
|
// cancel any previous loading task
|
||||||
if (this.loadDocumentTask) {
|
if (this.loadDocumentTask) {
|
||||||
this.loadDocumentTask.destroy()
|
this.loadDocumentTask.destroy().catch(debugConsole.error)
|
||||||
this.loadDocumentTask = undefined
|
this.loadDocumentTask = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<PDFJS.PDFDocumentProxy>((resolve, reject) => {
|
||||||
const rangeTransport = this.genPdfCachingTransport({
|
const rangeTransport = generatePdfCachingTransportFactory()({
|
||||||
url,
|
url,
|
||||||
pdfFile,
|
pdfFile,
|
||||||
abortController,
|
abortController,
|
||||||
|
@ -87,35 +76,25 @@ export default class PDFJSWrapper {
|
||||||
// custom range transport. Restore it by bumping the chunk size.
|
// custom range transport. Restore it by bumping the chunk size.
|
||||||
rangeChunkSize = pdfFile.size
|
rangeChunkSize = pdfFile.size
|
||||||
}
|
}
|
||||||
this.loadDocumentTask = this.PDFJS.getDocument({
|
this.loadDocumentTask = loadPdfDocumentFromUrl(url, {
|
||||||
url,
|
|
||||||
cMapUrl: this.cMapUrl,
|
|
||||||
cMapPacked: true,
|
|
||||||
standardFontDataUrl: this.standardFontDataUrl,
|
|
||||||
disableFontFace,
|
|
||||||
rangeChunkSize,
|
rangeChunkSize,
|
||||||
disableAutoFetch: true,
|
|
||||||
disableStream,
|
|
||||||
isEvalSupported: false,
|
|
||||||
textLayerMode: 2, // PDFJSViewer.TextLayerMode.ENABLE,
|
|
||||||
range: rangeTransport,
|
range: rangeTransport,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.loadDocumentTask.promise
|
this.loadDocumentTask.promise
|
||||||
.then(doc => {
|
.then(doc => {
|
||||||
if (!this.loadDocumentTask) {
|
if (!this.loadDocumentTask || !this.viewer) {
|
||||||
return // ignoring the response since loading task has been aborted
|
return // ignoring the response since loading task has been aborted
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousDoc = this.viewer.pdfDocument
|
const previousDoc = this.viewer.pdfDocument
|
||||||
|
|
||||||
this.viewer.setDocument(doc)
|
this.viewer.setDocument(doc)
|
||||||
this.linkService.setDocument(doc)
|
this.linkService.setDocument(doc)
|
||||||
resolve(doc)
|
resolve(doc)
|
||||||
|
|
||||||
if (previousDoc) {
|
if (previousDoc) {
|
||||||
previousDoc.cleanup().catch(debugConsole.error)
|
previousDoc.cleanup().catch(debugConsole.error)
|
||||||
previousDoc.destroy()
|
previousDoc.destroy().catch(debugConsole.error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@ -163,7 +142,7 @@ export default class PDFJSWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the page and offset of a click event
|
// get the page and offset of a click event
|
||||||
clickPosition(event, canvas, page) {
|
clickPosition(event: MouseEvent, canvas: HTMLCanvasElement, page: number) {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -205,7 +184,7 @@ export default class PDFJSWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToPosition(position, scale = null) {
|
scrollToPosition(position: Record<string, any>, scale = null) {
|
||||||
const destArray = [
|
const destArray = [
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
|
@ -239,13 +218,9 @@ export default class PDFJSWrapper {
|
||||||
this.loadDocumentTask = undefined
|
this.loadDocumentTask = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
async destroy() {
|
||||||
if (this.loadDocumentTask) {
|
await this.loadDocumentTask?.destroy().catch(debugConsole.error)
|
||||||
this.loadDocumentTask.destroy()
|
|
||||||
this.loadDocumentTask = undefined
|
this.loadDocumentTask = undefined
|
||||||
}
|
await this.viewer.pdfDocument?.destroy().catch(debugConsole.error)
|
||||||
if (this.viewer) {
|
|
||||||
this.viewer.destroy()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
40
services/web/frontend/js/features/pdf-preview/util/pdf-js.ts
Normal file
40
services/web/frontend/js/features/pdf-preview/util/pdf-js.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'core-js/stable/global-this' // polyfill for globalThis (used by pdf.js)
|
||||||
|
import 'core-js/stable/promise/all-settled' // polyfill for Promise.allSettled (used by pdf.js)
|
||||||
|
import 'core-js/stable/structured-clone' // polyfill for global.StructuredClone (used by pdf.js)
|
||||||
|
import 'core-js/stable/array/at' // polyfill for Array.prototype.at (used by pdf.js)
|
||||||
|
|
||||||
|
import { createWorker } from '@/utils/worker'
|
||||||
|
import * as PDFJS from 'pdfjs-dist/legacy/build/pdf.mjs'
|
||||||
|
import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api'
|
||||||
|
|
||||||
|
export { PDFJS }
|
||||||
|
|
||||||
|
createWorker(() => {
|
||||||
|
PDFJS.GlobalWorkerOptions.workerPort = new Worker(
|
||||||
|
new URL('pdfjs-dist/legacy/build/pdf.worker.mjs', import.meta.url) // NOTE: .mjs extension
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const imageResourcesPath = '/images/pdfjs-dist/'
|
||||||
|
const cMapUrl = '/js/pdfjs-dist/cmaps/'
|
||||||
|
const standardFontDataUrl = '/fonts/pdfjs-dist/'
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const disableFontFace = params.get('disable-font-face') === 'true'
|
||||||
|
const disableStream = process.env.NODE_ENV !== 'test'
|
||||||
|
|
||||||
|
export const loadPdfDocumentFromUrl = (
|
||||||
|
url: string,
|
||||||
|
options: Partial<DocumentInitParameters> = {}
|
||||||
|
) =>
|
||||||
|
PDFJS.getDocument({
|
||||||
|
url,
|
||||||
|
cMapUrl,
|
||||||
|
standardFontDataUrl,
|
||||||
|
disableFontFace,
|
||||||
|
disableAutoFetch: true, // only fetch the data needed for the displayed pages
|
||||||
|
disableStream,
|
||||||
|
isEvalSupported: false,
|
||||||
|
enableXfa: false, // default is false (2021-10-12), but set explicitly to be sure
|
||||||
|
...options,
|
||||||
|
})
|
|
@ -140,14 +140,16 @@ export class GraphicsWidget extends WidgetType {
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPDF(view: EditorView, canvas: HTMLCanvasElement, url: string) {
|
async renderPDF(view: EditorView, canvas: HTMLCanvasElement, url: string) {
|
||||||
const { PDFJS } = await this.importPDFJS()
|
const { loadPdfDocumentFromUrl } = await import(
|
||||||
|
'@/features/pdf-preview/util/pdf-js'
|
||||||
|
)
|
||||||
|
|
||||||
// bail out if loading PDF.js took too long
|
// bail out if loading PDF.js took too long
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdf = await PDFJS.getDocument({ url, isEvalSupported: false }).promise
|
const pdf = await loadPdfDocumentFromUrl(url).promise
|
||||||
const page = await pdf.getPage(1)
|
const page = await pdf.getPage(1)
|
||||||
|
|
||||||
// bail out if loading the PDF took too long
|
// bail out if loading the PDF took too long
|
||||||
|
@ -162,16 +164,10 @@ export class GraphicsWidget extends WidgetType {
|
||||||
canvas.style.width = width
|
canvas.style.width = width
|
||||||
canvas.style.maxWidth = width
|
canvas.style.maxWidth = width
|
||||||
page.render({
|
page.render({
|
||||||
canvasContext: canvas.getContext('2d'),
|
canvasContext: canvas.getContext('2d')!,
|
||||||
viewport,
|
viewport,
|
||||||
})
|
})
|
||||||
this.height = viewport.height
|
this.height = viewport.height
|
||||||
view.requestMeasure()
|
view.requestMeasure()
|
||||||
}
|
}
|
||||||
|
|
||||||
async importPDFJS(): Promise<any> {
|
|
||||||
return import('../../../../pdf-preview/util/pdf-js-versions').then(
|
|
||||||
m => m.default
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue