@libresign/pdf-elements 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/components/DraggableElement.vue.d.ts +240 -0
- package/dist/components/PDFElements.vue.d.ts +552 -0
- package/dist/components/PDFPage.vue.d.ts +40 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +1293 -0
- package/dist/pdf-uHvE5neP.mjs +17593 -0
- package/dist/pdf.worker.min-DmO9Xdfo.mjs +4 -0
- package/dist/types.d.ts +30 -0
- package/dist/utils/asyncReader.d.ts +10 -0
- package/dist/utils/geometry.d.ts +5 -0
- package/dist/utils/measurements.d.ts +12 -0
- package/dist/utils/objectStore.d.ts +5 -0
- package/dist/utils/pageBounds.d.ts +5 -0
- package/dist/utils/zoom.d.ts +2 -0
- package/package.json +46 -21
- package/src/components/DraggableElement.vue +18 -15
- package/src/components/PDFElements.vue +135 -60
- package/src/components/PDFPage.vue +11 -7
- package/src/index.ts +18 -0
- package/src/types.ts +35 -0
- package/src/utils/asyncReader.ts +103 -0
- package/src/utils/{geometry.js → geometry.ts} +16 -2
- package/src/utils/{measurements.js → measurements.ts} +20 -1
- package/src/utils/{objectStore.js → objectStore.ts} +15 -4
- package/src/utils/{pageBounds.js → pageBounds.ts} +2 -2
- package/src/utils/{zoom.js → zoom.ts} +3 -1
- package/dist/demo.html +0 -1
- package/dist/pdf-elements.common.js +0 -31698
- package/dist/pdf-elements.common.js.map +0 -1
- package/dist/pdf-elements.css +0 -1
- package/dist/pdf-elements.umd.js +0 -31710
- package/dist/pdf-elements.umd.js.map +0 -1
- package/dist/pdf-elements.umd.min.js +0 -2
- package/dist/pdf-elements.umd.min.js.map +0 -1
- package/dist/pdf.worker.min.mjs +0 -22
- package/src/index.js +0 -17
- package/src/utils/asyncReader.js +0 -44
|
@@ -25,6 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
25
25
|
class="overlay"
|
|
26
26
|
@mousemove="handleMouseMove"
|
|
27
27
|
@touchmove="handleMouseMove"
|
|
28
|
+
@click="handleOverlayClick(docIndex, pIndex, $event)"
|
|
29
|
+
@touchend="handleOverlayClick(docIndex, pIndex, $event)"
|
|
28
30
|
>
|
|
29
31
|
<div
|
|
30
32
|
v-if="isAddingMode && previewPageDocIndex === docIndex && previewPageIndex === pIndex && previewElement && previewVisible"
|
|
@@ -137,18 +139,21 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
137
139
|
</div>
|
|
138
140
|
</template>
|
|
139
141
|
|
|
140
|
-
<script>
|
|
142
|
+
<script lang="ts">
|
|
143
|
+
import { defineComponent, type PropType, markRaw } from 'vue'
|
|
141
144
|
import PDFPage from './PDFPage.vue'
|
|
142
145
|
import DraggableElement from './DraggableElement.vue'
|
|
143
|
-
import { readAsPDF, readAsArrayBuffer } from '../utils/asyncReader
|
|
144
|
-
import { clampPosition, getVisibleArea } from '../utils/geometry
|
|
145
|
-
import { getViewportWindow, isPageInViewport } from '../utils/pageBounds
|
|
146
|
-
import { applyScaleToDocs } from '../utils/zoom
|
|
147
|
-
import { objectIdExistsInDoc, findObjectPageIndex, updateObjectInDoc, removeObjectFromDoc } from '../utils/objectStore
|
|
148
|
-
import { getCachedMeasurement } from '../utils/measurements
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
import { readAsPDF, readAsArrayBuffer } from '../utils/asyncReader'
|
|
147
|
+
import { clampPosition, getVisibleArea } from '../utils/geometry'
|
|
148
|
+
import { getViewportWindow, isPageInViewport } from '../utils/pageBounds'
|
|
149
|
+
import { applyScaleToDocs } from '../utils/zoom'
|
|
150
|
+
import { objectIdExistsInDoc, findObjectPageIndex, updateObjectInDoc, removeObjectFromDoc } from '../utils/objectStore'
|
|
151
|
+
import { getCachedMeasurement } from '../utils/measurements'
|
|
152
|
+
import type { PDFDocumentEntry, PDFElementObject } from '../types'
|
|
153
|
+
|
|
154
|
+
export default defineComponent({
|
|
151
155
|
name: 'PDFElements',
|
|
156
|
+
emits: ['pdf-elements:end-init', 'pdf-elements:delete-object', 'pdf-elements:object-click'],
|
|
152
157
|
components: {
|
|
153
158
|
PDFPage,
|
|
154
159
|
DraggableElement,
|
|
@@ -163,11 +168,13 @@ export default {
|
|
|
163
168
|
default: '100%',
|
|
164
169
|
},
|
|
165
170
|
initFiles: {
|
|
166
|
-
type: Array
|
|
171
|
+
type: Array as PropType<
|
|
172
|
+
(string | Blob | ArrayBuffer | ArrayBufferView | Record<string, unknown>)[]
|
|
173
|
+
>,
|
|
167
174
|
default: () => [],
|
|
168
175
|
},
|
|
169
176
|
initFileNames: {
|
|
170
|
-
type: Array
|
|
177
|
+
type: Array as PropType<string[]>,
|
|
171
178
|
default: () => [],
|
|
172
179
|
},
|
|
173
180
|
initialScale: {
|
|
@@ -194,8 +201,12 @@ export default {
|
|
|
194
201
|
type: Boolean,
|
|
195
202
|
default: false,
|
|
196
203
|
},
|
|
204
|
+
emitObjectClick: {
|
|
205
|
+
type: Boolean,
|
|
206
|
+
default: false,
|
|
207
|
+
},
|
|
197
208
|
ignoreClickOutsideSelectors: {
|
|
198
|
-
type: Array
|
|
209
|
+
type: Array as PropType<string[]>,
|
|
199
210
|
default: () => [],
|
|
200
211
|
},
|
|
201
212
|
pageCountFormat: {
|
|
@@ -206,32 +217,36 @@ export default {
|
|
|
206
217
|
type: Boolean,
|
|
207
218
|
default: false,
|
|
208
219
|
},
|
|
220
|
+
pdfjsOptions: {
|
|
221
|
+
type: Object as PropType<Record<string, unknown>>,
|
|
222
|
+
default: () => ({}),
|
|
223
|
+
},
|
|
209
224
|
},
|
|
210
225
|
data() {
|
|
211
226
|
return {
|
|
212
227
|
scale: this.initialScale,
|
|
213
|
-
pdfDocuments: [],
|
|
228
|
+
pdfDocuments: [] as PDFDocumentEntry[],
|
|
214
229
|
selectedDocIndex: -1,
|
|
215
230
|
selectedPageIndex: -1,
|
|
216
231
|
isAddingMode: false,
|
|
217
|
-
previewElement: null,
|
|
232
|
+
previewElement: null as PDFElementObject | null,
|
|
218
233
|
previewPosition: { x: 0, y: 0 },
|
|
219
234
|
previewScale: { x: 1, y: 1 },
|
|
220
235
|
previewPageDocIndex: -1,
|
|
221
236
|
previewPageIndex: -1,
|
|
222
237
|
previewVisible: false,
|
|
223
238
|
hoverRafId: 0,
|
|
224
|
-
pendingHoverClientPos: null,
|
|
225
|
-
lastHoverRect: null,
|
|
239
|
+
pendingHoverClientPos: null as { x: number; y: number } | null,
|
|
240
|
+
lastHoverRect: null as DOMRect | null,
|
|
226
241
|
addingListenersAttached: false,
|
|
227
242
|
dragRafId: 0,
|
|
228
|
-
pendingDragClientPos: null,
|
|
243
|
+
pendingDragClientPos: null as { x: number; y: number } | null,
|
|
229
244
|
pageBoundsVersion: 0,
|
|
230
245
|
lastScrollTop: 0,
|
|
231
246
|
lastClientWidth: 0,
|
|
232
247
|
nextObjectCounter: 0,
|
|
233
248
|
isDraggingElement: false,
|
|
234
|
-
draggingObject: null,
|
|
249
|
+
draggingObject: null as PDFElementObject | null,
|
|
235
250
|
draggingDocIndex: -1,
|
|
236
251
|
draggingPageIndex: -1,
|
|
237
252
|
draggingClientPosition: { x: 0, y: 0 },
|
|
@@ -240,22 +255,20 @@ export default {
|
|
|
240
255
|
draggingElementShift: { x: 0, y: 0 },
|
|
241
256
|
lastMouseClientPos: { x: 0, y: 0 },
|
|
242
257
|
viewportRafId: 0,
|
|
243
|
-
objectIndexCache: {},
|
|
244
|
-
zoomRafId: null,
|
|
245
|
-
wheelZoomRafId: null,
|
|
246
|
-
boundHandleWheel: null,
|
|
258
|
+
objectIndexCache: markRaw({}) as Record<string, number>,
|
|
259
|
+
zoomRafId: null as number | null,
|
|
260
|
+
wheelZoomRafId: null as number | null,
|
|
261
|
+
boundHandleWheel: null as ((event: WheelEvent) => void) | null,
|
|
247
262
|
visualScale: this.initialScale,
|
|
248
263
|
autoFitApplied: false,
|
|
249
264
|
lastContainerWidth: 0,
|
|
265
|
+
_pagesBoundingRects: markRaw({}) as Record<string, { docIndex: number; pageIndex: number; rect: DOMRect }>,
|
|
266
|
+
_pagesBoundingRectsList: markRaw([]) as { docIndex: number; pageIndex: number; rect: DOMRect }[],
|
|
267
|
+
_pageMeasurementCache: markRaw({}) as Record<string, { width: number; height: number }>,
|
|
268
|
+
_lastPageBoundsScrollTop: 0,
|
|
269
|
+
_lastPageBoundsClientHeight: 0,
|
|
250
270
|
}
|
|
251
271
|
},
|
|
252
|
-
created() {
|
|
253
|
-
this._pagesBoundingRects = {}
|
|
254
|
-
this._pagesBoundingRectsList = []
|
|
255
|
-
this._pageMeasurementCache = {}
|
|
256
|
-
this._lastPageBoundsScrollTop = 0
|
|
257
|
-
this._lastPageBoundsClientHeight = 0
|
|
258
|
-
},
|
|
259
272
|
mounted() {
|
|
260
273
|
this.boundHandleWheel = this.handleWheel.bind(this)
|
|
261
274
|
this.init()
|
|
@@ -295,7 +308,7 @@ export default {
|
|
|
295
308
|
methods: {
|
|
296
309
|
async init() {
|
|
297
310
|
if (!this.initFiles || this.initFiles.length === 0) return
|
|
298
|
-
const docs = []
|
|
311
|
+
const docs: PDFDocumentEntry[] = []
|
|
299
312
|
this.autoFitApplied = false
|
|
300
313
|
|
|
301
314
|
for (let i = 0; i < this.initFiles.length; i++) {
|
|
@@ -305,9 +318,9 @@ export default {
|
|
|
305
318
|
let pdfDoc
|
|
306
319
|
if (file instanceof Blob) {
|
|
307
320
|
const buffer = await readAsArrayBuffer(file)
|
|
308
|
-
pdfDoc = await readAsPDF({ data: buffer })
|
|
321
|
+
pdfDoc = await readAsPDF({ data: buffer }, this.pdfjsOptions)
|
|
309
322
|
} else {
|
|
310
|
-
pdfDoc = await readAsPDF(file)
|
|
323
|
+
pdfDoc = await readAsPDF(file, this.pdfjsOptions)
|
|
311
324
|
}
|
|
312
325
|
|
|
313
326
|
const pages = []
|
|
@@ -323,12 +336,14 @@ export default {
|
|
|
323
336
|
pages.push(pagePromise)
|
|
324
337
|
}
|
|
325
338
|
|
|
339
|
+
const rawPages = markRaw(pages)
|
|
340
|
+
|
|
326
341
|
docs.push({
|
|
327
342
|
name,
|
|
328
343
|
file,
|
|
329
|
-
pdfDoc,
|
|
344
|
+
pdfDoc: markRaw(pdfDoc),
|
|
330
345
|
numPages: pdfDoc.numPages,
|
|
331
|
-
pages,
|
|
346
|
+
pages: rawPages,
|
|
332
347
|
pageWidths,
|
|
333
348
|
pagesScale: Array(pdfDoc.numPages).fill(this.scale),
|
|
334
349
|
allObjects: Array(pdfDoc.numPages).fill(0).map(() => []),
|
|
@@ -336,7 +351,7 @@ export default {
|
|
|
336
351
|
}
|
|
337
352
|
|
|
338
353
|
this.pdfDocuments = docs
|
|
339
|
-
this._pageMeasurementCache = {}
|
|
354
|
+
this._pageMeasurementCache = markRaw({})
|
|
340
355
|
if (docs.length) {
|
|
341
356
|
this.selectedDocIndex = 0
|
|
342
357
|
this.selectedPageIndex = 0
|
|
@@ -388,7 +403,12 @@ export default {
|
|
|
388
403
|
updateDraggingPosition(clientX, clientY) {
|
|
389
404
|
if (!this.isDraggingElement) return
|
|
390
405
|
|
|
391
|
-
this.pendingDragClientPos
|
|
406
|
+
if (this.pendingDragClientPos) {
|
|
407
|
+
this.pendingDragClientPos.x = clientX
|
|
408
|
+
this.pendingDragClientPos.y = clientY
|
|
409
|
+
} else {
|
|
410
|
+
this.pendingDragClientPos = { x: clientX, y: clientY }
|
|
411
|
+
}
|
|
392
412
|
if (this.dragRafId) return
|
|
393
413
|
this.dragRafId = window.requestAnimationFrame(() => {
|
|
394
414
|
this.dragRafId = 0
|
|
@@ -447,6 +467,7 @@ export default {
|
|
|
447
467
|
|
|
448
468
|
cachePageBounds() {
|
|
449
469
|
const nextRects = {}
|
|
470
|
+
const nextList = []
|
|
450
471
|
const container = this.$el
|
|
451
472
|
const scrollTop = container?.scrollTop || 0
|
|
452
473
|
const viewHeight = container?.clientHeight || 0
|
|
@@ -471,22 +492,24 @@ export default {
|
|
|
471
492
|
}
|
|
472
493
|
}
|
|
473
494
|
const rect = canvas.getBoundingClientRect()
|
|
474
|
-
|
|
495
|
+
const entry = {
|
|
475
496
|
docIndex: docIdx,
|
|
476
497
|
pageIndex: pageIdx,
|
|
477
498
|
rect,
|
|
478
499
|
}
|
|
500
|
+
nextRects[`${docIdx}-${pageIdx}`] = entry
|
|
501
|
+
nextList.push(entry)
|
|
479
502
|
}
|
|
480
503
|
}
|
|
481
|
-
this._pagesBoundingRects = nextRects
|
|
482
|
-
this._pagesBoundingRectsList =
|
|
504
|
+
this._pagesBoundingRects = markRaw(nextRects)
|
|
505
|
+
this._pagesBoundingRectsList = markRaw(nextList)
|
|
483
506
|
this.pageBoundsVersion++
|
|
484
507
|
},
|
|
485
508
|
cachePageBoundsForPage(docIndex, pageIndex) {
|
|
486
509
|
const canvas = this.getPageCanvasElement(docIndex, pageIndex)
|
|
487
510
|
if (!canvas) return
|
|
488
511
|
const rect = canvas.getBoundingClientRect()
|
|
489
|
-
|
|
512
|
+
const nextRects = {
|
|
490
513
|
...this._pagesBoundingRects,
|
|
491
514
|
[`${docIndex}-${pageIndex}`]: {
|
|
492
515
|
docIndex,
|
|
@@ -494,7 +517,8 @@ export default {
|
|
|
494
517
|
rect,
|
|
495
518
|
},
|
|
496
519
|
}
|
|
497
|
-
this.
|
|
520
|
+
this._pagesBoundingRects = markRaw(nextRects)
|
|
521
|
+
this._pagesBoundingRectsList = markRaw(Object.values(nextRects))
|
|
498
522
|
this.pageBoundsVersion++
|
|
499
523
|
},
|
|
500
524
|
getPageBoundsMap() {
|
|
@@ -520,7 +544,7 @@ export default {
|
|
|
520
544
|
},
|
|
521
545
|
|
|
522
546
|
getDisplayedPageScale(docIndex, pageIndex) {
|
|
523
|
-
this.pageBoundsVersion
|
|
547
|
+
void this.pageBoundsVersion
|
|
524
548
|
const doc = this.pdfDocuments[docIndex]
|
|
525
549
|
if (!doc) return 1
|
|
526
550
|
const baseWidth = doc.pageWidths?.[pageIndex] || 0
|
|
@@ -583,7 +607,12 @@ export default {
|
|
|
583
607
|
if (!this.isAddingMode || !this.previewElement) return
|
|
584
608
|
const { x, y } = this.getPointerPosition(event)
|
|
585
609
|
if (x === undefined || y === undefined) return
|
|
586
|
-
this.pendingHoverClientPos
|
|
610
|
+
if (this.pendingHoverClientPos) {
|
|
611
|
+
this.pendingHoverClientPos.x = x
|
|
612
|
+
this.pendingHoverClientPos.y = y
|
|
613
|
+
} else {
|
|
614
|
+
this.pendingHoverClientPos = { x, y }
|
|
615
|
+
}
|
|
587
616
|
if (this.hoverRafId) return
|
|
588
617
|
this.hoverRafId = window.requestAnimationFrame(() => {
|
|
589
618
|
this.hoverRafId = 0
|
|
@@ -603,11 +632,11 @@ export default {
|
|
|
603
632
|
rect: this.lastHoverRect,
|
|
604
633
|
}
|
|
605
634
|
} else {
|
|
606
|
-
const
|
|
635
|
+
const rectEntries = this.getPageBoundsList().length
|
|
607
636
|
? this.getPageBoundsList()
|
|
608
637
|
: Object.values(this.getPageBoundsMap())
|
|
609
|
-
for (let i = 0; i <
|
|
610
|
-
const entry =
|
|
638
|
+
for (let i = 0; i < rectEntries.length; i++) {
|
|
639
|
+
const entry = rectEntries[i]
|
|
611
640
|
const rect = entry.rect
|
|
612
641
|
if (cursorX >= rect.left && cursorX <= rect.right &&
|
|
613
642
|
cursorY >= rect.top && cursorY <= rect.bottom) {
|
|
@@ -651,6 +680,49 @@ export default {
|
|
|
651
680
|
this.previewVisible = true
|
|
652
681
|
})
|
|
653
682
|
},
|
|
683
|
+
handleOverlayClick(docIndex, pageIndex, event) {
|
|
684
|
+
if (!this.emitObjectClick) return
|
|
685
|
+
|
|
686
|
+
const { x: clientX, y: clientY } = this.getPointerPosition(event)
|
|
687
|
+
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) return
|
|
688
|
+
|
|
689
|
+
this.cachePageBoundsForPage(docIndex, pageIndex)
|
|
690
|
+
const pageRect = this.getPageRect(docIndex, pageIndex)
|
|
691
|
+
if (!pageRect) return
|
|
692
|
+
|
|
693
|
+
const pagesScale = this.getDisplayedPageScale(docIndex, pageIndex) || 1
|
|
694
|
+
const relX = (clientX - pageRect.left) / pagesScale
|
|
695
|
+
const relY = (clientY - pageRect.top) / pagesScale
|
|
696
|
+
|
|
697
|
+
const doc = this.pdfDocuments?.[docIndex]
|
|
698
|
+
const pageObjects = doc?.allObjects?.[pageIndex] || []
|
|
699
|
+
if (pageObjects.length === 0) return
|
|
700
|
+
let hitObject = null
|
|
701
|
+
|
|
702
|
+
for (let i = pageObjects.length - 1; i >= 0; i--) {
|
|
703
|
+
const object = pageObjects[i]
|
|
704
|
+
const x = Number(object.x)
|
|
705
|
+
const y = Number(object.y)
|
|
706
|
+
const width = Number(object.width)
|
|
707
|
+
const height = Number(object.height)
|
|
708
|
+
if (![x, y, width, height].every(Number.isFinite)) {
|
|
709
|
+
continue
|
|
710
|
+
}
|
|
711
|
+
if (relX >= x && relX <= x + width && relY >= y && relY <= y + height) {
|
|
712
|
+
hitObject = object
|
|
713
|
+
break
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (!hitObject) return
|
|
718
|
+
|
|
719
|
+
this.$emit('pdf-elements:object-click', {
|
|
720
|
+
docIndex,
|
|
721
|
+
pageIndex,
|
|
722
|
+
object: hitObject,
|
|
723
|
+
event,
|
|
724
|
+
})
|
|
725
|
+
},
|
|
654
726
|
|
|
655
727
|
handleKeyDown(event) {
|
|
656
728
|
if (event.key === 'Escape' && this.isAddingMode) {
|
|
@@ -679,7 +751,7 @@ export default {
|
|
|
679
751
|
|
|
680
752
|
applyScaleToDocs(this.pdfDocuments, this.scale)
|
|
681
753
|
|
|
682
|
-
this._pageMeasurementCache = {}
|
|
754
|
+
this._pageMeasurementCache = markRaw({})
|
|
683
755
|
this.cachePageBounds()
|
|
684
756
|
},
|
|
685
757
|
|
|
@@ -746,7 +818,7 @@ export default {
|
|
|
746
818
|
if (!this.addingListenersAttached) return
|
|
747
819
|
this.addingListenersAttached = false
|
|
748
820
|
document.removeEventListener('mousemove', this.handleMouseMove)
|
|
749
|
-
document.removeEventListener('touchmove', this.handleMouseMove
|
|
821
|
+
document.removeEventListener('touchmove', this.handleMouseMove)
|
|
750
822
|
document.removeEventListener('mouseup', this.finishAdding)
|
|
751
823
|
document.removeEventListener('touchend', this.finishAdding)
|
|
752
824
|
document.removeEventListener('keydown', this.handleKeyDown)
|
|
@@ -765,8 +837,9 @@ export default {
|
|
|
765
837
|
objectToAdd = { ...objectToAdd, id: this.generateObjectId() }
|
|
766
838
|
}
|
|
767
839
|
|
|
768
|
-
const
|
|
769
|
-
const
|
|
840
|
+
const measurement = this.getCachedMeasurement(docIndex, pageIndex, pageRef)
|
|
841
|
+
const pageWidth = measurement.width
|
|
842
|
+
const pageHeight = measurement.height
|
|
770
843
|
|
|
771
844
|
if (objectToAdd.x < 0 || objectToAdd.y < 0 ||
|
|
772
845
|
objectToAdd.x + objectToAdd.width > pageWidth ||
|
|
@@ -814,11 +887,11 @@ export default {
|
|
|
814
887
|
pageNumber: pageIndex + 1,
|
|
815
888
|
scale: pagesScale,
|
|
816
889
|
normalizedCoordinates: {
|
|
817
|
-
llx:
|
|
818
|
-
lly:
|
|
819
|
-
ury:
|
|
820
|
-
width:
|
|
821
|
-
height:
|
|
890
|
+
llx: Math.round(object.x),
|
|
891
|
+
lly: Math.round(normalizedCanvasHeight - object.y),
|
|
892
|
+
ury: Math.round(normalizedCanvasHeight - object.y - object.height),
|
|
893
|
+
width: Math.round(object.width),
|
|
894
|
+
height: Math.round(object.height),
|
|
822
895
|
},
|
|
823
896
|
})
|
|
824
897
|
})
|
|
@@ -1026,9 +1099,11 @@ export default {
|
|
|
1026
1099
|
if (!targetObject) return currentPageIndex
|
|
1027
1100
|
|
|
1028
1101
|
let targetPageIndex = currentPageIndex
|
|
1102
|
+
const pageBoundsList = this.getPageBoundsList()
|
|
1029
1103
|
const pageBoundsMap = this.getPageBoundsMap()
|
|
1030
|
-
|
|
1031
|
-
|
|
1104
|
+
const boundsEntries = pageBoundsList.length ? pageBoundsList : Object.values(pageBoundsMap)
|
|
1105
|
+
for (let i = 0; i < boundsEntries.length; i++) {
|
|
1106
|
+
const { docIndex: rectDocIndex, pageIndex, rect } = boundsEntries[i]
|
|
1032
1107
|
if (rectDocIndex === docIndex &&
|
|
1033
1108
|
mouseX >= rect.left && mouseX <= rect.right &&
|
|
1034
1109
|
mouseY >= rect.top && mouseY <= rect.bottom) {
|
|
@@ -1138,7 +1213,7 @@ export default {
|
|
|
1138
1213
|
this.scheduleAutoFitZoom()
|
|
1139
1214
|
return
|
|
1140
1215
|
}
|
|
1141
|
-
const canvases = this.$el?.querySelectorAll('canvas')
|
|
1216
|
+
const canvases = this.$el?.querySelectorAll('canvas') as NodeListOf<HTMLCanvasElement> | undefined
|
|
1142
1217
|
if (!canvases?.length) return
|
|
1143
1218
|
maxCanvasWidth = Math.max(...Array.from(canvases).map(canvas =>
|
|
1144
1219
|
canvas.width / (this.scale || 1),
|
|
@@ -1151,12 +1226,12 @@ export default {
|
|
|
1151
1226
|
this.scale = optimalScale
|
|
1152
1227
|
this.visualScale = optimalScale
|
|
1153
1228
|
applyScaleToDocs(this.pdfDocuments, this.scale)
|
|
1154
|
-
this._pageMeasurementCache = {}
|
|
1229
|
+
this._pageMeasurementCache = markRaw({})
|
|
1155
1230
|
this.cachePageBounds()
|
|
1156
1231
|
}
|
|
1157
1232
|
},
|
|
1158
1233
|
},
|
|
1159
|
-
}
|
|
1234
|
+
})
|
|
1160
1235
|
</script>
|
|
1161
1236
|
|
|
1162
1237
|
<style scoped>
|
|
@@ -7,12 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
7
7
|
<canvas ref="canvas" />
|
|
8
8
|
</template>
|
|
9
9
|
|
|
10
|
-
<script>
|
|
11
|
-
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { defineComponent, type PropType } from 'vue'
|
|
12
|
+
|
|
13
|
+
export default defineComponent({
|
|
12
14
|
name: 'PDFPage',
|
|
15
|
+
emits: ['onMeasure'],
|
|
13
16
|
props: {
|
|
14
17
|
page: {
|
|
15
|
-
type: Promise
|
|
18
|
+
type: Object as PropType<Promise<unknown>>,
|
|
16
19
|
required: true,
|
|
17
20
|
},
|
|
18
21
|
scale: {
|
|
@@ -25,7 +28,7 @@ export default {
|
|
|
25
28
|
dynamicScale: this.scale,
|
|
26
29
|
isRendering: false,
|
|
27
30
|
pendingRender: false,
|
|
28
|
-
renderTask: null,
|
|
31
|
+
renderTask: null as { cancel: () => void; promise: Promise<void> } | null,
|
|
29
32
|
}
|
|
30
33
|
},
|
|
31
34
|
watch: {
|
|
@@ -41,7 +44,7 @@ export default {
|
|
|
41
44
|
if (this.renderTask) {
|
|
42
45
|
try {
|
|
43
46
|
this.renderTask.cancel()
|
|
44
|
-
} catch
|
|
47
|
+
} catch {
|
|
45
48
|
// Ignore render cancellation errors.
|
|
46
49
|
}
|
|
47
50
|
this.renderTask = null
|
|
@@ -73,12 +76,13 @@ export default {
|
|
|
73
76
|
if (this.renderTask) {
|
|
74
77
|
try {
|
|
75
78
|
this.renderTask.cancel()
|
|
76
|
-
} catch
|
|
79
|
+
} catch {
|
|
77
80
|
// Ignore render cancellation errors.
|
|
78
81
|
}
|
|
79
82
|
this.renderTask = null
|
|
80
83
|
}
|
|
81
84
|
const context = canvas.getContext('2d')
|
|
85
|
+
if (!context) return
|
|
82
86
|
const viewport = _page.getViewport({
|
|
83
87
|
scale: this.dynamicScale,
|
|
84
88
|
})
|
|
@@ -100,7 +104,7 @@ export default {
|
|
|
100
104
|
}
|
|
101
105
|
},
|
|
102
106
|
},
|
|
103
|
-
}
|
|
107
|
+
})
|
|
104
108
|
</script>
|
|
105
109
|
|
|
106
110
|
<style scoped>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import type { App } from 'vue'
|
|
5
|
+
import PDFElements from './components/PDFElements.vue'
|
|
6
|
+
|
|
7
|
+
export { setWorkerPath } from './utils/asyncReader'
|
|
8
|
+
export type { PDFDocumentEntry, PDFElementObject, PDFElementsPublicApi } from './types'
|
|
9
|
+
|
|
10
|
+
const install = (app: App) => {
|
|
11
|
+
const name = PDFElements.name || 'PDFElements'
|
|
12
|
+
app.component(name, PDFElements)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
;(PDFElements as { install?: (app: App) => void }).install = install
|
|
16
|
+
|
|
17
|
+
export default PDFElements
|
|
18
|
+
export { PDFElements }
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
export interface PDFElementObject {
|
|
5
|
+
id?: string
|
|
6
|
+
x: number
|
|
7
|
+
y: number
|
|
8
|
+
width: number
|
|
9
|
+
height: number
|
|
10
|
+
type?: string
|
|
11
|
+
label?: string
|
|
12
|
+
icon?: string
|
|
13
|
+
resizable?: boolean
|
|
14
|
+
[key: string]: unknown
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PDFDocumentEntry {
|
|
18
|
+
name: string
|
|
19
|
+
file: unknown
|
|
20
|
+
pdfDoc: unknown
|
|
21
|
+
numPages: number
|
|
22
|
+
pages: Promise<unknown>[]
|
|
23
|
+
pageWidths: number[]
|
|
24
|
+
pagesScale: number[]
|
|
25
|
+
allObjects: PDFElementObject[][]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PDFElementsPublicApi {
|
|
29
|
+
startAddingElement: (templateObject: PDFElementObject) => void
|
|
30
|
+
addObjectToPage: (object: PDFElementObject, pageIndex?: number, docIndex?: number) => boolean
|
|
31
|
+
getAllObjects: (docIndex?: number) => PDFElementObject[]
|
|
32
|
+
updateObject: (docIndex: number, objectId: string, payload: Partial<PDFElementObject>) => void
|
|
33
|
+
deleteObject: (docIndex: number, objectId: string) => void
|
|
34
|
+
duplicateObject: (docIndex: number, objectId: string) => void
|
|
35
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import type { PDFDocumentProxy, PDFWorker } from 'pdfjs-dist'
|
|
5
|
+
|
|
6
|
+
let sharedWorker: PDFWorker | null = null
|
|
7
|
+
let pdfjsPromise: Promise<typeof import('pdfjs-dist')> | null = null
|
|
8
|
+
let workerUrlPromise: Promise<string> | null = null
|
|
9
|
+
let workerSrcOverride: string | null = null
|
|
10
|
+
|
|
11
|
+
function loadPdfjs() {
|
|
12
|
+
if (!pdfjsPromise) {
|
|
13
|
+
pdfjsPromise = import('pdfjs-dist')
|
|
14
|
+
}
|
|
15
|
+
return pdfjsPromise
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function loadWorkerUrl() {
|
|
19
|
+
if (!workerUrlPromise) {
|
|
20
|
+
workerUrlPromise = import('pdfjs-dist/build/pdf.worker.min.mjs?url').then(
|
|
21
|
+
(mod) => mod.default as string
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
return workerUrlPromise
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function ensureWorkerSrc(pdfjs: typeof import('pdfjs-dist')) {
|
|
28
|
+
if (workerSrcOverride) {
|
|
29
|
+
pdfjs.GlobalWorkerOptions.workerSrc = workerSrcOverride
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
if (!pdfjs.GlobalWorkerOptions.workerSrc) {
|
|
33
|
+
pdfjs.GlobalWorkerOptions.workerSrc = await loadWorkerUrl()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function getSharedWorker(pdfjs: typeof import('pdfjs-dist')): Promise<PDFWorker> {
|
|
38
|
+
if (!sharedWorker) {
|
|
39
|
+
await ensureWorkerSrc(pdfjs)
|
|
40
|
+
sharedWorker = new pdfjs.PDFWorker({}) as PDFWorker
|
|
41
|
+
}
|
|
42
|
+
return sharedWorker
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function setWorkerPath(path: string) {
|
|
46
|
+
workerSrcOverride = path
|
|
47
|
+
if (pdfjsPromise) {
|
|
48
|
+
pdfjsPromise.then((pdfjs) => {
|
|
49
|
+
pdfjs.GlobalWorkerOptions.workerSrc = path
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readAsArrayBuffer(file: Blob): Promise<ArrayBuffer> {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const reader = new FileReader()
|
|
57
|
+
reader.onload = () => resolve(reader.result as ArrayBuffer)
|
|
58
|
+
reader.onerror = reject
|
|
59
|
+
reader.readAsArrayBuffer(file)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type PdfInput =
|
|
64
|
+
| string
|
|
65
|
+
| ArrayBuffer
|
|
66
|
+
| ArrayBufferView
|
|
67
|
+
| Blob
|
|
68
|
+
| {
|
|
69
|
+
url?: string
|
|
70
|
+
data?: ArrayBuffer | Uint8Array
|
|
71
|
+
[key: string]: unknown
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function readAsPDF(
|
|
75
|
+
file: PdfInput,
|
|
76
|
+
options: Record<string, unknown> = {}
|
|
77
|
+
): Promise<PDFDocumentProxy> {
|
|
78
|
+
const pdfjs = await loadPdfjs()
|
|
79
|
+
const worker = await getSharedWorker(pdfjs)
|
|
80
|
+
const isArrayBuffer = file instanceof ArrayBuffer
|
|
81
|
+
const isView = ArrayBuffer.isView(file)
|
|
82
|
+
const isBlob = typeof Blob !== 'undefined' && file instanceof Blob
|
|
83
|
+
|
|
84
|
+
if (file && typeof file === 'object' && !isArrayBuffer && !isView && !isBlob) {
|
|
85
|
+
return pdfjs.getDocument({ ...(file as Record<string, unknown>), ...options, worker }).promise
|
|
86
|
+
}
|
|
87
|
+
if (typeof file === 'string') {
|
|
88
|
+
return pdfjs.getDocument({ url: file, ...options, worker }).promise
|
|
89
|
+
}
|
|
90
|
+
if (isBlob) {
|
|
91
|
+
const data = await readAsArrayBuffer(file as Blob)
|
|
92
|
+
return pdfjs.getDocument({ data, ...options, worker }).promise
|
|
93
|
+
}
|
|
94
|
+
const data = isArrayBuffer
|
|
95
|
+
? (file as ArrayBuffer)
|
|
96
|
+
: new Uint8Array(
|
|
97
|
+
(file as ArrayBufferView).buffer,
|
|
98
|
+
(file as ArrayBufferView).byteOffset,
|
|
99
|
+
(file as ArrayBufferView).byteLength
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return pdfjs.getDocument({ data, ...options, worker }).promise
|
|
103
|
+
}
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
-
export function clampPosition(
|
|
4
|
+
export function clampPosition(
|
|
5
|
+
x: number,
|
|
6
|
+
y: number,
|
|
7
|
+
width: number,
|
|
8
|
+
height: number,
|
|
9
|
+
pageWidth: number,
|
|
10
|
+
pageHeight: number
|
|
11
|
+
) {
|
|
5
12
|
return {
|
|
6
13
|
x: Math.max(0, Math.min(x, pageWidth - width)),
|
|
7
14
|
y: Math.max(0, Math.min(y, pageHeight - height)),
|
|
8
15
|
}
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
export function getVisibleArea(
|
|
18
|
+
export function getVisibleArea(
|
|
19
|
+
newX: number,
|
|
20
|
+
newY: number,
|
|
21
|
+
objWidth: number,
|
|
22
|
+
objHeight: number,
|
|
23
|
+
pageWidth: number,
|
|
24
|
+
pageHeight: number
|
|
25
|
+
) {
|
|
12
26
|
const visibleLeft = Math.max(0, newX)
|
|
13
27
|
const visibleTop = Math.max(0, newY)
|
|
14
28
|
const visibleRight = Math.min(pageWidth, newX + objWidth)
|