@libresign/pdf-elements 0.2.3 → 0.2.4
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 +1 -0
- package/dist/pdf-elements.common.js +129 -157
- package/dist/pdf-elements.common.js.map +1 -1
- package/dist/pdf-elements.css +1 -1
- package/dist/pdf-elements.umd.js +129 -157
- package/dist/pdf-elements.umd.js.map +1 -1
- package/dist/pdf-elements.umd.min.js +2 -2
- package/dist/pdf-elements.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DraggableElement.vue +53 -20
- package/src/components/PDFElements.vue +80 -25
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libresign/pdf-elements",
|
|
3
3
|
"description": "PDF viewer with draggable and resizable element overlays for Vue 2",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"author": "LibreCode <contact@librecode.coop>",
|
|
6
6
|
"private": false,
|
|
7
7
|
"main": "dist/pdf-elements.umd.js",
|
|
@@ -120,6 +120,10 @@ export default {
|
|
|
120
120
|
showDefaultActions: {
|
|
121
121
|
type: Boolean,
|
|
122
122
|
default: true,
|
|
123
|
+
},
|
|
124
|
+
readOnly: {
|
|
125
|
+
type: Boolean,
|
|
126
|
+
default: false,
|
|
123
127
|
}
|
|
124
128
|
},
|
|
125
129
|
data() {
|
|
@@ -153,25 +157,41 @@ export default {
|
|
|
153
157
|
},
|
|
154
158
|
elementStyle() {
|
|
155
159
|
const scale = this.pagesScale || 1
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
+
const isDragging = this.mode === 'drag'
|
|
161
|
+
const isResizing = this.mode === 'resize'
|
|
162
|
+
const offsetX = isDragging ? this.offsetX : 0
|
|
163
|
+
const offsetY = isDragging ? this.offsetY : 0
|
|
164
|
+
const resizeOffsetX = isResizing ? this.resizeOffsetX : 0
|
|
165
|
+
const resizeOffsetY = isResizing ? this.resizeOffsetY : 0
|
|
166
|
+
const resizeOffsetW = isResizing ? this.resizeOffsetW : 0
|
|
167
|
+
const resizeOffsetH = isResizing ? this.resizeOffsetH : 0
|
|
168
|
+
const currentX = this.object.x + offsetX + resizeOffsetX
|
|
169
|
+
const currentY = this.object.y + offsetY + resizeOffsetY
|
|
170
|
+
const currentWidth = this.object.width + resizeOffsetW
|
|
171
|
+
const currentHeight = this.object.height + resizeOffsetH
|
|
160
172
|
return {
|
|
161
173
|
left: `${currentX * scale}px`,
|
|
162
174
|
top: `${currentY * scale}px`,
|
|
163
175
|
width: `${currentWidth * scale}px`,
|
|
164
176
|
height: `${currentHeight * scale}px`,
|
|
177
|
+
pointerEvents: this.readOnly ? 'none' : 'auto',
|
|
165
178
|
}
|
|
166
179
|
},
|
|
167
180
|
toolbarStyle() {
|
|
168
181
|
const scale = this.pagesScale || 1
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
182
|
+
const isDragging = this.mode === 'drag'
|
|
183
|
+
const isResizing = this.mode === 'resize'
|
|
184
|
+
const offsetX = isDragging ? this.offsetX : 0
|
|
185
|
+
const offsetY = isDragging ? this.offsetY : 0
|
|
186
|
+
const resizeOffsetX = isResizing ? this.resizeOffsetX : 0
|
|
187
|
+
const resizeOffsetY = isResizing ? this.resizeOffsetY : 0
|
|
188
|
+
const resizeOffsetW = isResizing ? this.resizeOffsetW : 0
|
|
189
|
+
const x = this.object.x + offsetX + resizeOffsetX
|
|
190
|
+
const y = this.object.y + offsetY + resizeOffsetY
|
|
191
|
+
const width = this.object.width + resizeOffsetW
|
|
172
192
|
return {
|
|
173
193
|
left: `${(x + width / 2) * scale}px`,
|
|
174
|
-
top: `${(y -
|
|
194
|
+
top: `${(y - 60) * scale}px`,
|
|
175
195
|
transform: 'translateX(-50%)',
|
|
176
196
|
}
|
|
177
197
|
},
|
|
@@ -203,6 +223,9 @@ export default {
|
|
|
203
223
|
},
|
|
204
224
|
methods: {
|
|
205
225
|
handleElementClick(event) {
|
|
226
|
+
if (this.readOnly) {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
206
229
|
if (event.target.closest('.delete-handle') || event.target.closest('[data-stop-drag]') || event.target.closest('.actions-toolbar')) {
|
|
207
230
|
return
|
|
208
231
|
}
|
|
@@ -220,6 +243,7 @@ export default {
|
|
|
220
243
|
this.startResize(direction, event)
|
|
221
244
|
},
|
|
222
245
|
startDrag(event) {
|
|
246
|
+
if (this.readOnly) return
|
|
223
247
|
if (event.target.classList.contains('delete')) return
|
|
224
248
|
if (event.target.classList.contains('resize-handle')) return
|
|
225
249
|
this.mode = 'drag'
|
|
@@ -256,6 +280,7 @@ export default {
|
|
|
256
280
|
window.addEventListener('touchend', this.boundStopInteraction)
|
|
257
281
|
},
|
|
258
282
|
startResize(direction, event) {
|
|
283
|
+
if (this.readOnly) return
|
|
259
284
|
this.mode = 'resize'
|
|
260
285
|
this.direction = direction
|
|
261
286
|
this.startX = event.type.includes('touch') ? event.touches[0].clientX : event.clientX
|
|
@@ -318,7 +343,7 @@ export default {
|
|
|
318
343
|
return
|
|
319
344
|
}
|
|
320
345
|
|
|
321
|
-
const minSize = 16
|
|
346
|
+
const minSize = 16 / (this.pagesScale || 1)
|
|
322
347
|
let newWidth = this.startWidth
|
|
323
348
|
let newHeight = this.startHeight
|
|
324
349
|
let newLeft = this.startLeft
|
|
@@ -471,35 +496,43 @@ export default {
|
|
|
471
496
|
box-shadow: inset 0 0 0 2px #2563eb;
|
|
472
497
|
}
|
|
473
498
|
.resize-handle {
|
|
499
|
+
all: unset;
|
|
474
500
|
position: absolute;
|
|
475
|
-
width:
|
|
476
|
-
height:
|
|
501
|
+
width: 12px;
|
|
502
|
+
height: 12px;
|
|
503
|
+
min-width: 0;
|
|
504
|
+
min-height: 0;
|
|
477
505
|
background: #2563eb;
|
|
478
506
|
border: 1px solid #ffffff;
|
|
479
|
-
border-radius:
|
|
507
|
+
border-radius: 3px;
|
|
480
508
|
padding: 0;
|
|
481
509
|
margin: 0;
|
|
510
|
+
line-height: 0;
|
|
511
|
+
font-size: 0;
|
|
512
|
+
box-sizing: border-box;
|
|
513
|
+
display: block;
|
|
514
|
+
appearance: none;
|
|
482
515
|
cursor: pointer;
|
|
483
516
|
z-index: 200;
|
|
484
517
|
}
|
|
485
518
|
.handle-top-left {
|
|
486
|
-
top: -
|
|
487
|
-
left: -
|
|
519
|
+
top: -7px;
|
|
520
|
+
left: -7px;
|
|
488
521
|
cursor: nwse-resize;
|
|
489
522
|
}
|
|
490
523
|
.handle-top-right {
|
|
491
|
-
top: -
|
|
492
|
-
right: -
|
|
524
|
+
top: -7px;
|
|
525
|
+
right: -7px;
|
|
493
526
|
cursor: nesw-resize;
|
|
494
527
|
}
|
|
495
528
|
.handle-bottom-left {
|
|
496
|
-
bottom: -
|
|
497
|
-
left: -
|
|
529
|
+
bottom: -7px;
|
|
530
|
+
left: -7px;
|
|
498
531
|
cursor: nesw-resize;
|
|
499
532
|
}
|
|
500
533
|
.handle-bottom-right {
|
|
501
|
-
bottom: -
|
|
502
|
-
right: -
|
|
534
|
+
bottom: -7px;
|
|
535
|
+
right: -7px;
|
|
503
536
|
cursor: nwse-resize;
|
|
504
537
|
}
|
|
505
538
|
</style>
|
|
@@ -8,7 +8,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
8
8
|
<div
|
|
9
9
|
v-if="pdfDocuments.length"
|
|
10
10
|
class="pages-container"
|
|
11
|
-
:style="{ transform: `scale(${visualScale / scale})`, transformOrigin: 'top center' }"
|
|
12
11
|
>
|
|
13
12
|
<div v-for="(pdfDoc, docIndex) in pdfDocuments" :key="docIndex">
|
|
14
13
|
<div v-for="(page, pIndex) in pdfDoc.pages" :key="`${docIndex}-${pIndex}`" class="page-slot">
|
|
@@ -24,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
24
23
|
/>
|
|
25
24
|
<div
|
|
26
25
|
class="overlay"
|
|
26
|
+
@mousemove="handleMouseMove"
|
|
27
|
+
@touchmove="handleMouseMove"
|
|
27
28
|
>
|
|
28
29
|
<div
|
|
29
30
|
v-if="isAddingMode && previewPageDocIndex === docIndex && previewPageIndex === pIndex && previewElement && previewVisible"
|
|
@@ -36,10 +37,16 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
36
37
|
}"
|
|
37
38
|
>
|
|
38
39
|
<slot
|
|
39
|
-
name="custom"
|
|
40
|
+
:name="previewElement.type ? `element-${previewElement.type}` : 'custom'"
|
|
40
41
|
:object="previewElement"
|
|
41
42
|
:isSelected="false"
|
|
42
|
-
|
|
43
|
+
>
|
|
44
|
+
<slot
|
|
45
|
+
name="custom"
|
|
46
|
+
:object="previewElement"
|
|
47
|
+
:isSelected="false"
|
|
48
|
+
/>
|
|
49
|
+
</slot>
|
|
43
50
|
</div>
|
|
44
51
|
|
|
45
52
|
<DraggableElement
|
|
@@ -47,9 +54,10 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
47
54
|
:key="object.id"
|
|
48
55
|
:ref="`draggable${docIndex}-${pIndex}-${object.id}`"
|
|
49
56
|
:object="object"
|
|
50
|
-
:pages-scale="
|
|
57
|
+
:pages-scale="getDisplayedPageScale(docIndex, pIndex)"
|
|
51
58
|
:page-width="getPageWidth(docIndex, pIndex)"
|
|
52
59
|
:page-height="getPageHeight(docIndex, pIndex)"
|
|
60
|
+
:read-only="readOnly"
|
|
53
61
|
:on-update="(payload) => updateObject(docIndex, object.id, payload)"
|
|
54
62
|
:on-delete="() => deleteObject(docIndex, object.id)"
|
|
55
63
|
:on-drag-start="(mouseX, mouseY, pointerOffset, dragShift) => startDraggingElement(docIndex, pIndex, object, mouseX, mouseY, pointerOffset, dragShift)"
|
|
@@ -127,7 +135,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
127
135
|
</template>
|
|
128
136
|
|
|
129
137
|
<script>
|
|
130
|
-
import debounce from 'debounce'
|
|
131
138
|
import PDFPage from './PDFPage.vue'
|
|
132
139
|
import DraggableElement from './DraggableElement.vue'
|
|
133
140
|
import { readAsPDF, readAsArrayBuffer } from '../utils/asyncReader.js'
|
|
@@ -175,6 +182,10 @@ export default {
|
|
|
175
182
|
type: Boolean,
|
|
176
183
|
default: true,
|
|
177
184
|
},
|
|
185
|
+
readOnly: {
|
|
186
|
+
type: Boolean,
|
|
187
|
+
default: false,
|
|
188
|
+
},
|
|
178
189
|
pageCountFormat: {
|
|
179
190
|
type: String,
|
|
180
191
|
default: '{currentPage} of {totalPages}',
|
|
@@ -210,15 +221,15 @@ export default {
|
|
|
210
221
|
viewportRafId: 0,
|
|
211
222
|
objectIndexCache: {},
|
|
212
223
|
zoomRafId: null,
|
|
224
|
+
wheelZoomRafId: null,
|
|
213
225
|
boundHandleWheel: null,
|
|
214
|
-
debouncedApplyZoom: null,
|
|
215
226
|
visualScale: this.initialScale,
|
|
216
227
|
autoFitApplied: false,
|
|
228
|
+
lastContainerWidth: 0,
|
|
217
229
|
}
|
|
218
230
|
},
|
|
219
231
|
mounted() {
|
|
220
232
|
this.boundHandleWheel = this.handleWheel.bind(this)
|
|
221
|
-
this.debouncedApplyZoom = debounce(this.commitZoom, 150)
|
|
222
233
|
this.init()
|
|
223
234
|
document.addEventListener('mousemove', this.handleMouseMove)
|
|
224
235
|
document.addEventListener('touchmove', this.handleMouseMove, { passive: true })
|
|
@@ -234,7 +245,10 @@ export default {
|
|
|
234
245
|
if (this.zoomRafId) {
|
|
235
246
|
cancelAnimationFrame(this.zoomRafId)
|
|
236
247
|
}
|
|
237
|
-
this.
|
|
248
|
+
if (this.wheelZoomRafId) {
|
|
249
|
+
cancelAnimationFrame(this.wheelZoomRafId)
|
|
250
|
+
this.wheelZoomRafId = null
|
|
251
|
+
}
|
|
238
252
|
if (this.boundHandleWheel) {
|
|
239
253
|
this.$el?.removeEventListener('wheel', this.boundHandleWheel)
|
|
240
254
|
}
|
|
@@ -400,24 +414,35 @@ export default {
|
|
|
400
414
|
},
|
|
401
415
|
|
|
402
416
|
cachePageBounds() {
|
|
403
|
-
|
|
417
|
+
const nextRects = {}
|
|
404
418
|
for (let docIdx = 0; docIdx < this.pdfDocuments.length; docIdx++) {
|
|
405
419
|
for (let pageIdx = 0; pageIdx < this.pdfDocuments[docIdx].pages.length; pageIdx++) {
|
|
406
420
|
const canvas = this.getPageCanvasElement(docIdx, pageIdx)
|
|
407
421
|
if (!canvas) continue
|
|
408
422
|
const rect = canvas.getBoundingClientRect()
|
|
409
|
-
|
|
423
|
+
nextRects[`${docIdx}-${pageIdx}`] = {
|
|
410
424
|
docIndex: docIdx,
|
|
411
425
|
pageIndex: pageIdx,
|
|
412
426
|
rect,
|
|
413
427
|
}
|
|
414
428
|
}
|
|
415
429
|
}
|
|
430
|
+
this.pagesBoundingRects = nextRects
|
|
416
431
|
},
|
|
417
432
|
|
|
418
433
|
getDisplayedPageScale(docIndex, pageIndex) {
|
|
419
434
|
const doc = this.pdfDocuments[docIndex]
|
|
420
435
|
if (!doc) return 1
|
|
436
|
+
const baseWidth = doc.pageWidths?.[pageIndex] || 0
|
|
437
|
+
const rectWidth = this.pagesBoundingRects[`${docIndex}-${pageIndex}`]?.rect?.width || 0
|
|
438
|
+
if (rectWidth && baseWidth) {
|
|
439
|
+
return rectWidth / baseWidth
|
|
440
|
+
}
|
|
441
|
+
const canvas = this.getPageCanvasElement(docIndex, pageIndex)
|
|
442
|
+
const fallbackRectWidth = canvas?.getBoundingClientRect?.().width || 0
|
|
443
|
+
if (fallbackRectWidth && baseWidth) {
|
|
444
|
+
return fallbackRectWidth / baseWidth
|
|
445
|
+
}
|
|
421
446
|
const base = doc.pagesScale[pageIndex] || 1
|
|
422
447
|
const factor = this.visualScale && this.scale ? (this.visualScale / this.scale) : 1
|
|
423
448
|
return base * factor
|
|
@@ -442,6 +467,14 @@ export default {
|
|
|
442
467
|
if (this.isAddingMode || this.isDraggingElement) {
|
|
443
468
|
this.cachePageBounds()
|
|
444
469
|
}
|
|
470
|
+
if (this.autoFitZoom && !this.isAddingMode && !this.isDraggingElement) {
|
|
471
|
+
const containerWidth = this.$el?.clientWidth || 0
|
|
472
|
+
if (containerWidth && containerWidth !== this.lastContainerWidth) {
|
|
473
|
+
this.lastContainerWidth = containerWidth
|
|
474
|
+
this.autoFitApplied = false
|
|
475
|
+
this.scheduleAutoFitZoom()
|
|
476
|
+
}
|
|
477
|
+
}
|
|
445
478
|
this.viewportRafId = 0
|
|
446
479
|
})
|
|
447
480
|
},
|
|
@@ -463,17 +496,17 @@ export default {
|
|
|
463
496
|
|
|
464
497
|
const canvasEl = this.getPageCanvasElement(docIndex, pageIndex)
|
|
465
498
|
const pagesScale = this.pdfDocuments[docIndex]?.pagesScale?.[pageIndex] || 1
|
|
466
|
-
const
|
|
467
|
-
const
|
|
468
|
-
const layoutScaleX =
|
|
469
|
-
const layoutScaleY =
|
|
499
|
+
const renderWidth = canvasEl?.width || rect.width
|
|
500
|
+
const renderHeight = canvasEl?.height || rect.height
|
|
501
|
+
const layoutScaleX = renderWidth ? rect.width / renderWidth : 1
|
|
502
|
+
const layoutScaleY = renderHeight ? rect.height / renderHeight : 1
|
|
470
503
|
const relX = (clientX - rect.left) / layoutScaleX / pagesScale
|
|
471
504
|
const relY = (clientY - rect.top) / layoutScaleY / pagesScale
|
|
472
505
|
|
|
473
|
-
const pageWidth =
|
|
474
|
-
const pageHeight =
|
|
475
|
-
this.previewScale.x = pagesScale
|
|
476
|
-
this.previewScale.y = pagesScale
|
|
506
|
+
const pageWidth = renderWidth / pagesScale
|
|
507
|
+
const pageHeight = renderHeight / pagesScale
|
|
508
|
+
this.previewScale.x = pagesScale
|
|
509
|
+
this.previewScale.y = pagesScale
|
|
477
510
|
let x = relX - this.previewElement.width / 2
|
|
478
511
|
let y = relY - this.previewElement.height / 2
|
|
479
512
|
|
|
@@ -506,7 +539,11 @@ export default {
|
|
|
506
539
|
const factor = 1 - (event.deltaY * 0.002)
|
|
507
540
|
const nextVisual = Math.max(0.5, Math.min(3.0, this.visualScale * factor))
|
|
508
541
|
this.visualScale = nextVisual
|
|
509
|
-
this.
|
|
542
|
+
if (this.wheelZoomRafId) return
|
|
543
|
+
this.wheelZoomRafId = window.requestAnimationFrame(() => {
|
|
544
|
+
this.wheelZoomRafId = null
|
|
545
|
+
this.commitZoom()
|
|
546
|
+
})
|
|
510
547
|
},
|
|
511
548
|
|
|
512
549
|
commitZoom() {
|
|
@@ -593,26 +630,27 @@ export default {
|
|
|
593
630
|
if (docIndex < 0 || docIndex >= this.pdfDocuments.length) return []
|
|
594
631
|
|
|
595
632
|
const doc = this.pdfDocuments[docIndex]
|
|
596
|
-
const scale = this.scale || 1
|
|
597
633
|
const result = []
|
|
598
634
|
|
|
599
635
|
doc.allObjects.forEach((pageObjects, pageIndex) => {
|
|
600
636
|
const pageRef = this.getPageComponent(docIndex, pageIndex)
|
|
601
637
|
if (!pageRef) return
|
|
602
|
-
|
|
603
638
|
const measurement = pageRef.getCanvasMeasurement()
|
|
604
|
-
const
|
|
639
|
+
const pagesScale = doc.pagesScale[pageIndex] || 1
|
|
640
|
+
const normalizedCanvasHeight = measurement.canvasHeight / pagesScale
|
|
605
641
|
|
|
606
642
|
pageObjects.forEach(object => {
|
|
607
643
|
result.push({
|
|
608
644
|
...object,
|
|
609
645
|
pageIndex,
|
|
610
646
|
pageNumber: pageIndex + 1,
|
|
611
|
-
scale,
|
|
647
|
+
scale: pagesScale,
|
|
612
648
|
normalizedCoordinates: {
|
|
613
649
|
llx: parseInt(object.x, 10),
|
|
614
650
|
lly: parseInt(normalizedCanvasHeight - object.y, 10),
|
|
615
651
|
ury: parseInt(normalizedCanvasHeight - object.y - object.height, 10),
|
|
652
|
+
width: parseInt(object.width, 10),
|
|
653
|
+
height: parseInt(object.height, 10),
|
|
616
654
|
},
|
|
617
655
|
})
|
|
618
656
|
})
|
|
@@ -729,10 +767,27 @@ export default {
|
|
|
729
767
|
deleteObject(docIndex, objectId) {
|
|
730
768
|
if (docIndex < 0 || docIndex >= this.pdfDocuments.length) return
|
|
731
769
|
const doc = this.pdfDocuments[docIndex]
|
|
732
|
-
|
|
733
|
-
|
|
770
|
+
let deletedObject = null
|
|
771
|
+
let deletedPageIndex = -1
|
|
772
|
+
|
|
773
|
+
doc.allObjects = doc.allObjects.map((objects, pageIndex) =>
|
|
774
|
+
objects.filter(object => {
|
|
775
|
+
if (object.id === objectId) {
|
|
776
|
+
deletedObject = object
|
|
777
|
+
deletedPageIndex = pageIndex
|
|
778
|
+
return false
|
|
779
|
+
}
|
|
780
|
+
return true
|
|
781
|
+
}),
|
|
734
782
|
)
|
|
735
783
|
delete this.objectIndexCache[`${docIndex}-${objectId}`]
|
|
784
|
+
if (deletedObject) {
|
|
785
|
+
this.$emit('pdf-elements:delete-object', {
|
|
786
|
+
object: deletedObject,
|
|
787
|
+
docIndex,
|
|
788
|
+
pageIndex: deletedPageIndex,
|
|
789
|
+
})
|
|
790
|
+
}
|
|
736
791
|
},
|
|
737
792
|
|
|
738
793
|
checkAndMoveObjectPage(docIndex, objectId, mouseX, mouseY) {
|