Skip to content

Commit fe9d456

Browse files
authored
feat: switch to PDF.js to preview (#951)
1 parent 8d19c3c commit fe9d456

File tree

3 files changed

+203
-81
lines changed

3 files changed

+203
-81
lines changed

datagouv-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"maplibre-gl": "^5.6.2",
5151
"ofetch": "^1.4.1",
5252
"ol": "^10.6.1",
53-
"pdf-vue3": "^1.0.12",
53+
"pdfjs-dist": "^4.10.38",
5454
"pmtiles": "^4.3.0",
5555
"popmotion": "^11.0",
5656
"rehype-highlight": "^7.0.2",
Lines changed: 70 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,15 @@
11
<template>
22
<div class="text-xs">
3-
<div v-if="pdfData">
4-
<!--
5-
We use props.resource.url instead of props.resource.latest
6-
because the PDF component raises an error otherwise.
7-
See https://github.com/datagouv/cdata/pull/611
8-
-->
9-
<PDF
10-
:src="props.resource.url"
11-
:show-progress="true"
12-
progress-color="#0063cb"
13-
:show-page-tooltip="true"
14-
:show-back-to-top-btn="true"
15-
:scroll-threshold="300"
16-
pdf-width="100%"
17-
:row-gap="12"
18-
:use-system-fonts="true"
19-
:disable-range="false"
20-
:disable-stream="false"
21-
:disable-auto-fetch="false"
3+
<div
4+
v-if="pdfReady"
5+
ref="containerRef"
6+
class="w-full overflow-y-auto max-h-[80vh] space-y-3"
7+
>
8+
<canvas
9+
v-for="page in totalPages"
10+
:key="page"
11+
:ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
2212
class="w-full"
23-
@on-progress="handleProgress"
24-
@on-complete="handleComplete"
25-
@on-page-change="handlePageChange"
26-
@on-pdf-init="handlePdfInit"
27-
@on-error="handleError"
2813
/>
2914
</div>
3015
<div
@@ -64,19 +49,18 @@
6449
</template>
6550

6651
<script setup lang="ts">
67-
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
52+
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
6853
import { RiErrorWarningLine } from '@remixicon/vue'
54+
import * as pdfjsLib from 'pdfjs-dist'
55+
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
56+
import type { PDFDocumentProxy } from 'pdfjs-dist'
6957
import SimpleBanner from '../SimpleBanner.vue'
7058
import { useComponentsConfig } from '../../config'
7159
import type { Resource } from '../../types/resources'
7260
import { useTranslation } from '../../composables/useTranslation'
7361
import { getResourceFilesize } from '../../functions/datasets'
7462
75-
const PDF = defineAsyncComponent(() =>
76-
import('pdf-vue3').then((module) => {
77-
return module.default
78-
}),
79-
)
63+
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
8064
8165
const props = defineProps<{
8266
resource: Resource
@@ -85,10 +69,47 @@ const props = defineProps<{
8569
const config = useComponentsConfig()
8670
const { t } = useTranslation()
8771
88-
const pdfData = ref<boolean>(false)
72+
const containerRef = ref<HTMLElement | null>(null)
73+
const pdfReady = ref(false)
8974
const loading = ref(false)
9075
const error = ref<string | null>(null)
9176
const fileTooLarge = ref(false)
77+
const totalPages = ref(0)
78+
79+
let pdfDoc: PDFDocumentProxy | null = null
80+
const canvasRefs = new Map<number, HTMLCanvasElement>()
81+
82+
function setCanvasRef(el: HTMLCanvasElement | null, pageNum: number) {
83+
if (el) canvasRefs.set(pageNum, el)
84+
}
85+
86+
async function renderPage(pageNum: number) {
87+
if (!pdfDoc) return
88+
89+
const canvas = canvasRefs.get(pageNum)
90+
if (!canvas) return
91+
92+
const page = await pdfDoc.getPage(pageNum)
93+
94+
const containerWidth = containerRef.value?.clientWidth ?? 800
95+
const unscaledViewport = page.getViewport({ scale: 1 })
96+
const scale = containerWidth / unscaledViewport.width
97+
const viewport = page.getViewport({ scale })
98+
99+
const dpr = window.devicePixelRatio || 1
100+
canvas.width = viewport.width * dpr
101+
canvas.height = viewport.height * dpr
102+
canvas.style.width = `${viewport.width}px`
103+
canvas.style.height = `${viewport.height}px`
104+
105+
const context = canvas.getContext('2d')!
106+
context.scale(dpr, dpr)
107+
108+
await page.render({
109+
canvasContext: context,
110+
viewport,
111+
}).promise
112+
}
92113
93114
const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
94115
@@ -105,7 +126,6 @@ const shouldLoadPdf = computed(() => {
105126
})
106127
107128
const loadPdf = async () => {
108-
// Check if file is too large or size is unknown before loading
109129
if (!shouldLoadPdf.value) {
110130
fileTooLarge.value = true
111131
return
@@ -115,65 +135,41 @@ const loadPdf = async () => {
115135
error.value = null
116136
117137
try {
118-
// Test if the PDF URL is accessible
119-
const response = await fetch(props.resource.url, { method: 'HEAD' })
120-
// const response = await fetch('/test-data.pdf') // For testing locally without CORS issues
121-
if (!response.ok) {
122-
throw new Error(`HTTP error! status: ${response.status}`)
123-
}
138+
const loadingTask = pdfjsLib.getDocument({
139+
url: props.resource.url,
140+
isEvalSupported: false,
141+
})
142+
143+
pdfDoc = await loadingTask.promise
144+
totalPages.value = pdfDoc.numPages
145+
pdfReady.value = true
124146
125-
// If the URL is accessible, set pdfData to true
126-
// The PDF component will handle the actual loading
127-
pdfData.value = true
147+
await nextTick()
148+
149+
for (let i = 1; i <= pdfDoc.numPages; i++) {
150+
await renderPage(i)
151+
}
128152
}
129153
catch (err) {
130-
console.error('Error testing PDF URL:', err)
154+
console.error('Error loading PDF:', err)
131155
132156
if (err instanceof TypeError) {
133157
error.value = 'network'
134158
}
135159
else {
136160
error.value = 'generic'
137161
}
138-
139-
pdfData.value = false
140162
}
141163
finally {
142164
loading.value = false
143165
}
144166
}
145167
146-
// Event handlers for PDF component
147-
const handleProgress = (loadRatio: number) => {
148-
console.log(`PDF loading progress: ${loadRatio}%`)
149-
}
150-
151-
const handleComplete = () => {
152-
console.log('PDF download completed')
153-
}
154-
155-
const handlePageChange = (page: number) => {
156-
console.log(`PDF page changed to: ${page}`)
157-
}
158-
159-
const handlePdfInit = (pdf: unknown) => {
160-
console.log('PDF initialized:', pdf)
161-
}
162-
163-
const handleError = (err: unknown) => {
164-
console.error('PDF loading error:', err)
165-
166-
if (err instanceof TypeError) {
167-
error.value = 'network'
168-
}
169-
else {
170-
error.value = 'generic'
171-
}
172-
173-
pdfData.value = false
174-
}
175-
176168
onMounted(() => {
177169
loadPdf()
178170
})
171+
172+
onBeforeUnmount(() => {
173+
pdfDoc?.destroy()
174+
})
179175
</script>

pnpm-lock.yaml

Lines changed: 132 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)