@libresign/pdf-elements 0.2.4 → 0.2.5

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.
@@ -138,6 +138,11 @@ SPDX-License-Identifier: AGPL-3.0-or-later
138
138
  import PDFPage from './PDFPage.vue'
139
139
  import DraggableElement from './DraggableElement.vue'
140
140
  import { readAsPDF, readAsArrayBuffer } from '../utils/asyncReader.js'
141
+ import { clampPosition, getVisibleArea } from '../utils/geometry.js'
142
+ import { getViewportWindow, isPageInViewport } from '../utils/pageBounds.js'
143
+ import { applyScaleToDocs } from '../utils/zoom.js'
144
+ import { objectIdExistsInDoc, findObjectPageIndex, updateObjectInDoc, removeObjectFromDoc } from '../utils/objectStore.js'
145
+ import { getCachedMeasurement } from '../utils/measurements.js'
141
146
 
142
147
  export default {
143
148
  name: 'PDFElements',
@@ -208,7 +213,16 @@ export default {
208
213
  previewPageDocIndex: -1,
209
214
  previewPageIndex: -1,
210
215
  previewVisible: false,
211
- pagesBoundingRects: {},
216
+ hoverRafId: 0,
217
+ pendingHoverClientPos: null,
218
+ lastHoverRect: null,
219
+ addingListenersAttached: false,
220
+ dragRafId: 0,
221
+ pendingDragClientPos: null,
222
+ pageBoundsVersion: 0,
223
+ lastScrollTop: 0,
224
+ lastClientWidth: 0,
225
+ nextObjectCounter: 0,
212
226
  isDraggingElement: false,
213
227
  draggingObject: null,
214
228
  draggingDocIndex: -1,
@@ -228,14 +242,16 @@ export default {
228
242
  lastContainerWidth: 0,
229
243
  }
230
244
  },
245
+ created() {
246
+ this._pagesBoundingRects = {}
247
+ this._pagesBoundingRectsList = []
248
+ this._pageMeasurementCache = {}
249
+ this._lastPageBoundsScrollTop = 0
250
+ this._lastPageBoundsClientHeight = 0
251
+ },
231
252
  mounted() {
232
253
  this.boundHandleWheel = this.handleWheel.bind(this)
233
254
  this.init()
234
- document.addEventListener('mousemove', this.handleMouseMove)
235
- document.addEventListener('touchmove', this.handleMouseMove, { passive: true })
236
- document.addEventListener('mouseup', this.finishAdding)
237
- document.addEventListener('touchend', this.finishAdding)
238
- document.addEventListener('keydown', this.handleKeyDown)
239
255
  window.addEventListener('scroll', this.onViewportScroll, { passive: true })
240
256
  window.addEventListener('resize', this.onViewportScroll)
241
257
  this.$el?.addEventListener('scroll', this.onViewportScroll, { passive: true })
@@ -252,11 +268,7 @@ export default {
252
268
  if (this.boundHandleWheel) {
253
269
  this.$el?.removeEventListener('wheel', this.boundHandleWheel)
254
270
  }
255
- document.removeEventListener('mousemove', this.handleMouseMove)
256
- document.removeEventListener('touchmove', this.handleMouseMove)
257
- document.removeEventListener('mouseup', this.finishAdding)
258
- document.removeEventListener('touchend', this.finishAdding)
259
- document.removeEventListener('keydown', this.handleKeyDown)
271
+ this.detachAddingListeners()
260
272
  window.removeEventListener('scroll', this.onViewportScroll)
261
273
  window.removeEventListener('resize', this.onViewportScroll)
262
274
  this.$el?.removeEventListener('scroll', this.onViewportScroll)
@@ -264,6 +276,14 @@ export default {
264
276
  window.cancelAnimationFrame(this.viewportRafId)
265
277
  this.viewportRafId = 0
266
278
  }
279
+ if (this.hoverRafId) {
280
+ window.cancelAnimationFrame(this.hoverRafId)
281
+ this.hoverRafId = 0
282
+ }
283
+ if (this.dragRafId) {
284
+ window.cancelAnimationFrame(this.dragRafId)
285
+ this.dragRafId = 0
286
+ }
267
287
  },
268
288
  methods: {
269
289
  async init() {
@@ -288,7 +308,7 @@ export default {
288
308
  for (let p = 1; p <= pdfDoc.numPages; p++) {
289
309
  const pagePromise = pdfDoc.getPage(p)
290
310
  pagePromise.then((page) => {
291
- pageWidths[p - 1] = page.getViewport({ scale: 1 }).width
311
+ pageWidths.splice(p - 1, 1, page.getViewport({ scale: 1 }).width)
292
312
  if (this.autoFitZoom) {
293
313
  this.scheduleAutoFitZoom()
294
314
  }
@@ -309,6 +329,7 @@ export default {
309
329
  }
310
330
 
311
331
  this.pdfDocuments = docs
332
+ this._pageMeasurementCache = {}
312
333
  if (docs.length) {
313
334
  this.selectedDocIndex = 0
314
335
  this.selectedPageIndex = 0
@@ -338,11 +359,8 @@ export default {
338
359
  ? dragShift
339
360
  : { x: 0, y: 0 }
340
361
 
341
- const pageKey = `${docIndex}-${pageIndex}`
342
- if (!this.pagesBoundingRects[pageKey]) {
343
- this.cachePageBounds()
344
- }
345
- const pageRect = this.pagesBoundingRects[pageKey]?.rect
362
+ this.cachePageBounds()
363
+ const pageRect = this.getPageRect(docIndex, pageIndex)
346
364
  if (pointerOffset && typeof pointerOffset.x === 'number' && typeof pointerOffset.y === 'number') {
347
365
  this.draggingInitialMouseOffset.x = pointerOffset.x
348
366
  this.draggingInitialMouseOffset.y = pointerOffset.y
@@ -358,17 +376,22 @@ export default {
358
376
  this.draggingClientPosition.y = mouseY - this.draggingInitialMouseOffset.y
359
377
  }
360
378
 
361
- this.cachePageBounds()
362
379
  },
363
380
 
364
381
  updateDraggingPosition(clientX, clientY) {
365
382
  if (!this.isDraggingElement) return
366
383
 
367
- this.lastMouseClientPos.x = clientX
368
- this.lastMouseClientPos.y = clientY
369
-
370
- this.draggingClientPosition.x = clientX - this.draggingInitialMouseOffset.x
371
- this.draggingClientPosition.y = clientY - this.draggingInitialMouseOffset.y
384
+ this.pendingDragClientPos = { x: clientX, y: clientY }
385
+ if (this.dragRafId) return
386
+ this.dragRafId = window.requestAnimationFrame(() => {
387
+ this.dragRafId = 0
388
+ const pending = this.pendingDragClientPos
389
+ if (!pending) return
390
+ this.lastMouseClientPos.x = pending.x
391
+ this.lastMouseClientPos.y = pending.y
392
+ this.draggingClientPosition.x = pending.x - this.draggingInitialMouseOffset.x
393
+ this.draggingClientPosition.y = pending.y - this.draggingInitialMouseOffset.y
394
+ })
372
395
  },
373
396
 
374
397
  stopDraggingElement() {
@@ -400,10 +423,12 @@ export default {
400
423
  this.draggingDocIndex = -1
401
424
  this.draggingPageIndex = -1
402
425
  this.draggingElementShift = { x: 0, y: 0 }
426
+ this.pendingDragClientPos = null
403
427
  },
404
428
 
405
429
  startAddingElement(templateObject) {
406
430
  if (!this.pdfDocuments.length) return
431
+ this.attachAddingListeners()
407
432
  this.isAddingMode = true
408
433
  this.previewElement = { ...templateObject }
409
434
  this.previewPageDocIndex = 0
@@ -415,10 +440,29 @@ export default {
415
440
 
416
441
  cachePageBounds() {
417
442
  const nextRects = {}
443
+ const container = this.$el
444
+ const scrollTop = container?.scrollTop || 0
445
+ const viewHeight = container?.clientHeight || 0
446
+ if (!this.isAddingMode && !this.isDraggingElement &&
447
+ scrollTop === this._lastPageBoundsScrollTop &&
448
+ viewHeight === this._lastPageBoundsClientHeight) {
449
+ return
450
+ }
451
+ this._lastPageBoundsScrollTop = scrollTop
452
+ this._lastPageBoundsClientHeight = viewHeight
453
+ const { minY, maxY } = getViewportWindow(scrollTop, viewHeight)
418
454
  for (let docIdx = 0; docIdx < this.pdfDocuments.length; docIdx++) {
419
455
  for (let pageIdx = 0; pageIdx < this.pdfDocuments[docIdx].pages.length; pageIdx++) {
420
456
  const canvas = this.getPageCanvasElement(docIdx, pageIdx)
421
457
  if (!canvas) continue
458
+ if (viewHeight) {
459
+ const wrapper = canvas.closest('.page-wrapper') || canvas
460
+ const offsetTop = wrapper.offsetTop || 0
461
+ const offsetHeight = wrapper.offsetHeight || 0
462
+ if (!isPageInViewport(offsetTop, offsetHeight, minY, maxY)) {
463
+ continue
464
+ }
465
+ }
422
466
  const rect = canvas.getBoundingClientRect()
423
467
  nextRects[`${docIdx}-${pageIdx}`] = {
424
468
  docIndex: docIdx,
@@ -427,31 +471,71 @@ export default {
427
471
  }
428
472
  }
429
473
  }
430
- this.pagesBoundingRects = nextRects
474
+ this._pagesBoundingRects = nextRects
475
+ this._pagesBoundingRectsList = Object.values(nextRects)
476
+ this.pageBoundsVersion++
477
+ },
478
+ cachePageBoundsForPage(docIndex, pageIndex) {
479
+ const canvas = this.getPageCanvasElement(docIndex, pageIndex)
480
+ if (!canvas) return
481
+ const rect = canvas.getBoundingClientRect()
482
+ this._pagesBoundingRects = {
483
+ ...this._pagesBoundingRects,
484
+ [`${docIndex}-${pageIndex}`]: {
485
+ docIndex,
486
+ pageIndex,
487
+ rect,
488
+ },
489
+ }
490
+ this._pagesBoundingRectsList = Object.values(this._pagesBoundingRects)
491
+ this.pageBoundsVersion++
492
+ },
493
+ getPageBoundsMap() {
494
+ return this._pagesBoundingRects || {}
495
+ },
496
+ getPageBoundsList() {
497
+ return this._pagesBoundingRectsList || []
498
+ },
499
+ getPageRect(docIndex, pageIndex) {
500
+ return this.getPageBoundsMap()[`${docIndex}-${pageIndex}`]?.rect || null
501
+ },
502
+ getPointerPosition(event) {
503
+ if (event?.type?.includes?.('touch')) {
504
+ return {
505
+ x: event.touches?.[0]?.clientX,
506
+ y: event.touches?.[0]?.clientY,
507
+ }
508
+ }
509
+ return {
510
+ x: event?.clientX,
511
+ y: event?.clientY,
512
+ }
431
513
  },
432
514
 
433
515
  getDisplayedPageScale(docIndex, pageIndex) {
516
+ this.pageBoundsVersion
434
517
  const doc = this.pdfDocuments[docIndex]
435
518
  if (!doc) return 1
436
519
  const baseWidth = doc.pageWidths?.[pageIndex] || 0
437
- const rectWidth = this.pagesBoundingRects[`${docIndex}-${pageIndex}`]?.rect?.width || 0
520
+ const pageBoundsMap = this.getPageBoundsMap()
521
+ if (!pageBoundsMap[`${docIndex}-${pageIndex}`]) {
522
+ this.cachePageBoundsForPage(docIndex, pageIndex)
523
+ }
524
+ const rectWidth = this.getPageBoundsMap()[`${docIndex}-${pageIndex}`]?.rect?.width || 0
438
525
  if (rectWidth && baseWidth) {
439
526
  return rectWidth / baseWidth
440
527
  }
441
- const canvas = this.getPageCanvasElement(docIndex, pageIndex)
442
- const fallbackRectWidth = canvas?.getBoundingClientRect?.().width || 0
443
- if (fallbackRectWidth && baseWidth) {
444
- return fallbackRectWidth / baseWidth
528
+ if (this.isAddingMode || this.isDraggingElement) {
529
+ const canvas = this.getPageCanvasElement(docIndex, pageIndex)
530
+ const fallbackRectWidth = canvas?.getBoundingClientRect?.().width || 0
531
+ if (fallbackRectWidth && baseWidth) {
532
+ return fallbackRectWidth / baseWidth
533
+ }
445
534
  }
446
535
  const base = doc.pagesScale[pageIndex] || 1
447
536
  const factor = this.visualScale && this.scale ? (this.visualScale / this.scale) : 1
448
537
  return base * factor
449
538
  },
450
- getRenderPageScale(docIndex, pageIndex) {
451
- const doc = this.pdfDocuments[docIndex]
452
- if (!doc) return 1
453
- return doc.pagesScale[pageIndex] || 1
454
- },
455
539
  getPageComponent(docIndex, pageIndex) {
456
540
  const pageRef = this.$refs[`page${docIndex}-${pageIndex}`]
457
541
  return pageRef && Array.isArray(pageRef) && pageRef[0] ? pageRef[0] : null
@@ -464,13 +548,22 @@ export default {
464
548
  onViewportScroll() {
465
549
  if (this.viewportRafId) return
466
550
  this.viewportRafId = window.requestAnimationFrame(() => {
551
+ const container = this.$el
552
+ const scrollTop = container?.scrollTop || 0
553
+ const clientWidth = container?.clientWidth || 0
554
+ const scrollChanged = scrollTop !== this.lastScrollTop
555
+ const widthChanged = clientWidth !== this.lastClientWidth
556
+ this.lastScrollTop = scrollTop
557
+ this.lastClientWidth = clientWidth
558
+
467
559
  if (this.isAddingMode || this.isDraggingElement) {
468
- this.cachePageBounds()
560
+ if (scrollChanged || widthChanged) {
561
+ this.cachePageBounds()
562
+ }
469
563
  }
470
564
  if (this.autoFitZoom && !this.isAddingMode && !this.isDraggingElement) {
471
- const containerWidth = this.$el?.clientWidth || 0
472
- if (containerWidth && containerWidth !== this.lastContainerWidth) {
473
- this.lastContainerWidth = containerWidth
565
+ if (clientWidth && widthChanged) {
566
+ this.lastContainerWidth = clientWidth
474
567
  this.autoFitApplied = false
475
568
  this.scheduleAutoFitZoom()
476
569
  }
@@ -481,49 +574,75 @@ export default {
481
574
 
482
575
  handleMouseMove(event) {
483
576
  if (!this.isAddingMode || !this.previewElement) return
577
+ const { x, y } = this.getPointerPosition(event)
578
+ if (x === undefined || y === undefined) return
579
+ this.pendingHoverClientPos = { x, y }
580
+ if (this.hoverRafId) return
581
+ this.hoverRafId = window.requestAnimationFrame(() => {
582
+ this.hoverRafId = 0
583
+ const pending = this.pendingHoverClientPos
584
+ if (!pending) return
585
+
586
+ const cursorX = pending.x
587
+ const cursorY = pending.y
588
+ let target = null
589
+
590
+ if (this.lastHoverRect &&
591
+ cursorX >= this.lastHoverRect.left && cursorX <= this.lastHoverRect.right &&
592
+ cursorY >= this.lastHoverRect.top && cursorY <= this.lastHoverRect.bottom) {
593
+ target = {
594
+ docIndex: this.previewPageDocIndex,
595
+ pageIndex: this.previewPageIndex,
596
+ rect: this.lastHoverRect,
597
+ }
598
+ } else {
599
+ const rects = this.getPageBoundsList().length
600
+ ? this.getPageBoundsList()
601
+ : Object.values(this.getPageBoundsMap())
602
+ for (let i = 0; i < rects.length; i++) {
603
+ const entry = rects[i]
604
+ const rect = entry.rect
605
+ if (cursorX >= rect.left && cursorX <= rect.right &&
606
+ cursorY >= rect.top && cursorY <= rect.bottom) {
607
+ target = entry
608
+ break
609
+ }
610
+ }
611
+ }
484
612
 
485
- const clientX = event.type.includes('touch') ? event.touches[0].clientX : event.clientX
486
- const clientY = event.type.includes('touch') ? event.touches[0].clientY : event.clientY
487
-
488
- let foundPage = false
489
- for (const key in this.pagesBoundingRects) {
490
- const { docIndex, pageIndex, rect } = this.pagesBoundingRects[key]
491
- if (clientX >= rect.left && clientX <= rect.right &&
492
- clientY >= rect.top && clientY <= rect.bottom) {
493
- this.previewPageDocIndex = docIndex
494
- this.previewPageIndex = pageIndex
495
- foundPage = true
496
-
497
- const canvasEl = this.getPageCanvasElement(docIndex, pageIndex)
498
- const pagesScale = this.pdfDocuments[docIndex]?.pagesScale?.[pageIndex] || 1
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
503
- const relX = (clientX - rect.left) / layoutScaleX / pagesScale
504
- const relY = (clientY - rect.top) / layoutScaleY / pagesScale
505
-
506
- const pageWidth = renderWidth / pagesScale
507
- const pageHeight = renderHeight / pagesScale
508
- this.previewScale.x = pagesScale
509
- this.previewScale.y = pagesScale
510
- let x = relX - this.previewElement.width / 2
511
- let y = relY - this.previewElement.height / 2
512
-
513
- x = Math.max(0, Math.min(x, pageWidth - this.previewElement.width))
514
- y = Math.max(0, Math.min(y, pageHeight - this.previewElement.height))
515
-
516
- this.previewPosition.x = x
517
- this.previewPosition.y = y
518
- this.previewVisible = true
519
- break
613
+ if (!target) {
614
+ this.previewVisible = false
615
+ this.previewScale = { x: 1, y: 1 }
616
+ this.lastHoverRect = null
617
+ return
520
618
  }
521
- }
522
619
 
523
- if (!foundPage) {
524
- this.previewVisible = false
525
- this.previewScale = { x: 1, y: 1 }
526
- }
620
+ this.previewPageDocIndex = target.docIndex
621
+ this.previewPageIndex = target.pageIndex
622
+ this.lastHoverRect = target.rect
623
+ const canvasEl = this.getPageCanvasElement(target.docIndex, target.pageIndex)
624
+ const pagesScale = this.pdfDocuments[target.docIndex]?.pagesScale?.[target.pageIndex] || 1
625
+ const renderWidth = canvasEl?.width || target.rect.width
626
+ const renderHeight = canvasEl?.height || target.rect.height
627
+ const layoutScaleX = renderWidth ? target.rect.width / renderWidth : 1
628
+ const layoutScaleY = renderHeight ? target.rect.height / renderHeight : 1
629
+ const relX = (cursorX - target.rect.left) / layoutScaleX / pagesScale
630
+ const relY = (cursorY - target.rect.top) / layoutScaleY / pagesScale
631
+
632
+ const pageWidth = renderWidth / pagesScale
633
+ const pageHeight = renderHeight / pagesScale
634
+ this.previewScale.x = pagesScale
635
+ this.previewScale.y = pagesScale
636
+ let x = relX - this.previewElement.width / 2
637
+ let y = relY - this.previewElement.height / 2
638
+
639
+ x = Math.max(0, Math.min(x, pageWidth - this.previewElement.width))
640
+ y = Math.max(0, Math.min(y, pageHeight - this.previewElement.height))
641
+
642
+ this.previewPosition.x = x
643
+ this.previewPosition.y = y
644
+ this.previewVisible = true
645
+ })
527
646
  },
528
647
 
529
648
  handleKeyDown(event) {
@@ -551,10 +670,9 @@ export default {
551
670
 
552
671
  this.scale = newScale
553
672
 
554
- this.pdfDocuments.forEach((doc) => {
555
- doc.pagesScale = doc.pagesScale.map(() => this.scale)
556
- })
673
+ applyScaleToDocs(this.pdfDocuments, this.scale)
557
674
 
675
+ this._pageMeasurementCache = {}
558
676
  this.cachePageBounds()
559
677
  },
560
678
 
@@ -564,7 +682,7 @@ export default {
564
682
 
565
683
  const objectToAdd = {
566
684
  ...this.previewElement,
567
- id: `obj-${Date.now()}`,
685
+ id: this.generateObjectId(),
568
686
  x: Math.round(this.previewPosition.x),
569
687
  y: Math.round(this.previewPosition.y),
570
688
  }
@@ -601,6 +719,30 @@ export default {
601
719
  this.isAddingMode = false
602
720
  this.previewElement = null
603
721
  this.previewVisible = false
722
+ this.detachAddingListeners()
723
+ },
724
+ generateObjectId() {
725
+ const counter = this.nextObjectCounter++
726
+ const rand = Math.random().toString(36).slice(2, 8)
727
+ return `obj-${Date.now()}-${counter}-${rand}`
728
+ },
729
+ attachAddingListeners() {
730
+ if (this.addingListenersAttached) return
731
+ this.addingListenersAttached = true
732
+ document.addEventListener('mousemove', this.handleMouseMove)
733
+ document.addEventListener('touchmove', this.handleMouseMove, { passive: true })
734
+ document.addEventListener('mouseup', this.finishAdding)
735
+ document.addEventListener('touchend', this.finishAdding)
736
+ document.addEventListener('keydown', this.handleKeyDown)
737
+ },
738
+ detachAddingListeners() {
739
+ if (!this.addingListenersAttached) return
740
+ this.addingListenersAttached = false
741
+ document.removeEventListener('mousemove', this.handleMouseMove)
742
+ document.removeEventListener('touchmove', this.handleMouseMove, { passive: true })
743
+ document.removeEventListener('mouseup', this.finishAdding)
744
+ document.removeEventListener('touchend', this.finishAdding)
745
+ document.removeEventListener('keydown', this.handleKeyDown)
604
746
  },
605
747
 
606
748
  addObjectToPage(object, pageIndex = this.selectedPageIndex, docIndex = this.selectedDocIndex) {
@@ -611,20 +753,39 @@ export default {
611
753
  const pageRef = this.getPageComponent(docIndex, pageIndex)
612
754
  if (!pageRef) return false
613
755
 
756
+ let objectToAdd = object
757
+ if (!objectToAdd.id || this.objectIdExists(docIndex, objectToAdd.id)) {
758
+ objectToAdd = { ...objectToAdd, id: this.generateObjectId() }
759
+ }
760
+
614
761
  const pageWidth = this.getPageWidth(docIndex, pageIndex)
615
762
  const pageHeight = this.getPageHeight(docIndex, pageIndex)
616
763
 
617
- if (object.x < 0 || object.y < 0 ||
618
- object.x + object.width > pageWidth ||
619
- object.y + object.height > pageHeight) {
764
+ if (objectToAdd.x < 0 || objectToAdd.y < 0 ||
765
+ objectToAdd.x + objectToAdd.width > pageWidth ||
766
+ objectToAdd.y + objectToAdd.height > pageHeight) {
620
767
  return false
621
768
  }
622
769
 
623
- doc.allObjects = doc.allObjects.map((objects, pIndex) =>
624
- pIndex === pageIndex ? [...objects, object] : objects,
625
- )
770
+ doc.allObjects[pageIndex].push(objectToAdd)
771
+ this.objectIndexCache[`${docIndex}-${objectToAdd.id}`] = pageIndex
626
772
  return true
627
773
  },
774
+ objectIdExists(docIndex, objectId) {
775
+ if (!objectId) return false
776
+ const cacheKey = `${docIndex}-${objectId}`
777
+ if (this.objectIndexCache[cacheKey] !== undefined) return true
778
+ const doc = this.pdfDocuments[docIndex]
779
+ return objectIdExistsInDoc(doc, objectId)
780
+ },
781
+ updateObjectInPage(docIndex, pageIndex, objectId, payload) {
782
+ const doc = this.pdfDocuments[docIndex]
783
+ updateObjectInDoc(doc, pageIndex, objectId, payload)
784
+ },
785
+ removeObjectFromPage(docIndex, pageIndex, objectId) {
786
+ const doc = this.pdfDocuments[docIndex]
787
+ removeObjectFromDoc(doc, pageIndex, objectId)
788
+ },
628
789
 
629
790
  getAllObjects(docIndex = this.selectedDocIndex) {
630
791
  if (docIndex < 0 || docIndex >= this.pdfDocuments.length) return []
@@ -635,9 +796,9 @@ export default {
635
796
  doc.allObjects.forEach((pageObjects, pageIndex) => {
636
797
  const pageRef = this.getPageComponent(docIndex, pageIndex)
637
798
  if (!pageRef) return
638
- const measurement = pageRef.getCanvasMeasurement()
799
+ const measurement = this.getCachedMeasurement(docIndex, pageIndex, pageRef)
800
+ const normalizedCanvasHeight = measurement.height
639
801
  const pagesScale = doc.pagesScale[pageIndex] || 1
640
- const normalizedCanvasHeight = measurement.canvasHeight / pagesScale
641
802
 
642
803
  pageObjects.forEach(object => {
643
804
  result.push({
@@ -667,12 +828,10 @@ export default {
667
828
  let currentPageIndex = this.objectIndexCache[cacheKey]
668
829
 
669
830
  if (currentPageIndex === undefined) {
670
- doc.allObjects.forEach((objects, pIndex) => {
671
- if (objects.find(o => o.id === objectId)) {
672
- currentPageIndex = pIndex
673
- this.objectIndexCache[cacheKey] = pIndex
674
- }
675
- })
831
+ currentPageIndex = findObjectPageIndex(doc, objectId)
832
+ if (currentPageIndex !== undefined) {
833
+ this.objectIndexCache[cacheKey] = currentPageIndex
834
+ }
676
835
  }
677
836
 
678
837
  if (currentPageIndex === undefined) return
@@ -684,19 +843,18 @@ export default {
684
843
  const mouseX = payload._mouseX
685
844
  const mouseY = payload._mouseY
686
845
 
687
- if (!this.pagesBoundingRects || Object.keys(this.pagesBoundingRects).length === 0) {
846
+ const pageBoundsMap = this.getPageBoundsMap()
847
+ if (!pageBoundsMap || Object.keys(pageBoundsMap).length === 0) {
688
848
  this.cachePageBounds()
689
849
  }
690
850
 
691
- const currentPageRect = this.pagesBoundingRects[`${docIndex}-${currentPageIndex}`]?.rect
851
+ const currentPageRect = this.getPageRect(docIndex, currentPageIndex)
692
852
  if (currentPageRect) {
693
853
  const pagesScale = this.getDisplayedPageScale(docIndex, currentPageIndex)
694
854
  const relX = (mouseX - currentPageRect.left - this.draggingElementShift.x) / pagesScale - (this.draggingInitialMouseOffset.x / pagesScale)
695
855
  const relY = (mouseY - currentPageRect.top - this.draggingElementShift.y) / pagesScale - (this.draggingInitialMouseOffset.y / pagesScale)
696
856
 
697
- doc.allObjects[currentPageIndex] = doc.allObjects[currentPageIndex].map(obj =>
698
- obj.id === objectId ? { ...obj, x: relX, y: relY } : obj
699
- )
857
+ this.updateObjectInPage(docIndex, currentPageIndex, objectId, { x: relX, y: relY })
700
858
  }
701
859
  return
702
860
  }
@@ -707,6 +865,14 @@ export default {
707
865
  const objWidth = payload.width !== undefined ? payload.width : targetObject.width
708
866
  const objHeight = payload.height !== undefined ? payload.height : targetObject.height
709
867
 
868
+ const { width: currentPageWidth, height: currentPageHeight } = this.getPageSize(docIndex, currentPageIndex)
869
+ if (newX >= 0 && newY >= 0 &&
870
+ newX + objWidth <= currentPageWidth &&
871
+ newY + objHeight <= currentPageHeight) {
872
+ this.updateObjectInPage(docIndex, currentPageIndex, objectId, payload)
873
+ return
874
+ }
875
+
710
876
  let bestPageIndex = currentPageIndex
711
877
  let maxVisibleArea = 0
712
878
 
@@ -714,30 +880,18 @@ export default {
714
880
  const pageWidth = this.getPageWidth(docIndex, pIndex)
715
881
  const pageHeight = this.getPageHeight(docIndex, pIndex)
716
882
 
717
- const visibleLeft = Math.max(0, newX)
718
- const visibleTop = Math.max(0, newY)
719
- const visibleRight = Math.min(pageWidth, newX + objWidth)
720
- const visibleBottom = Math.min(pageHeight, newY + objHeight)
721
-
722
- if (visibleRight > visibleLeft && visibleBottom > visibleTop) {
723
- const visibleArea = (visibleRight - visibleLeft) * (visibleBottom - visibleTop)
724
- if (visibleArea > maxVisibleArea) {
725
- maxVisibleArea = visibleArea
726
- bestPageIndex = pIndex
727
- }
883
+ const visibleArea = getVisibleArea(newX, newY, objWidth, objHeight, pageWidth, pageHeight)
884
+ if (visibleArea > maxVisibleArea) {
885
+ maxVisibleArea = visibleArea
886
+ bestPageIndex = pIndex
728
887
  }
729
888
  }
730
889
 
731
890
  if (bestPageIndex !== currentPageIndex) {
732
- const pageWidth = this.getPageWidth(docIndex, bestPageIndex)
733
- const pageHeight = this.getPageHeight(docIndex, bestPageIndex)
734
-
735
- const adjustedX = Math.max(0, Math.min(newX, pageWidth - objWidth))
736
- const adjustedY = Math.max(0, Math.min(newY, pageHeight - objHeight))
891
+ const { width: pageWidth, height: pageHeight } = this.getPageSize(docIndex, bestPageIndex)
892
+ const { x: adjustedX, y: adjustedY } = clampPosition(newX, newY, objWidth, objHeight, pageWidth, pageHeight)
737
893
 
738
- doc.allObjects[currentPageIndex] = doc.allObjects[currentPageIndex].filter(
739
- obj => obj.id !== objectId
740
- )
894
+ this.removeObjectFromPage(docIndex, currentPageIndex, objectId)
741
895
  const updatedObject = {
742
896
  ...targetObject,
743
897
  ...payload,
@@ -749,8 +903,7 @@ export default {
749
903
  return
750
904
  }
751
905
 
752
- const pageWidth = this.getPageWidth(docIndex, currentPageIndex)
753
- const pageHeight = this.getPageHeight(docIndex, currentPageIndex)
906
+ const { width: pageWidth, height: pageHeight } = this.getPageSize(docIndex, currentPageIndex)
754
907
 
755
908
  if (newX < 0 || newY < 0 ||
756
909
  newX + objWidth > pageWidth ||
@@ -759,9 +912,7 @@ export default {
759
912
  }
760
913
  }
761
914
 
762
- doc.allObjects = doc.allObjects.map(objects =>
763
- objects.map(object => (object.id === objectId ? { ...object, ...payload } : object)),
764
- )
915
+ this.updateObjectInPage(docIndex, currentPageIndex, objectId, payload)
765
916
  },
766
917
 
767
918
  deleteObject(docIndex, objectId) {
@@ -770,16 +921,16 @@ export default {
770
921
  let deletedObject = null
771
922
  let deletedPageIndex = -1
772
923
 
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
- }),
782
- )
924
+ doc.allObjects.some((objects, pageIndex) => {
925
+ const objectIndex = objects.findIndex(object => object.id === objectId)
926
+ if (objectIndex === -1) {
927
+ return false
928
+ }
929
+ deletedObject = objects[objectIndex]
930
+ deletedPageIndex = pageIndex
931
+ objects.splice(objectIndex, 1)
932
+ return true
933
+ })
783
934
  delete this.objectIndexCache[`${docIndex}-${objectId}`]
784
935
  if (deletedObject) {
785
936
  this.$emit('pdf-elements:delete-object', {
@@ -798,12 +949,10 @@ export default {
798
949
  let currentPageIndex = this.objectIndexCache[cacheKey]
799
950
 
800
951
  if (currentPageIndex === undefined) {
801
- doc.allObjects.forEach((objects, pIndex) => {
802
- if (objects.find(o => o.id === objectId)) {
803
- currentPageIndex = pIndex
804
- this.objectIndexCache[cacheKey] = pIndex
805
- }
806
- })
952
+ currentPageIndex = findObjectPageIndex(doc, objectId)
953
+ if (currentPageIndex !== undefined) {
954
+ this.objectIndexCache[cacheKey] = currentPageIndex
955
+ }
807
956
  }
808
957
 
809
958
  if (currentPageIndex === undefined) return undefined
@@ -812,8 +961,9 @@ export default {
812
961
  if (!targetObject) return currentPageIndex
813
962
 
814
963
  let targetPageIndex = currentPageIndex
815
- for (const key in this.pagesBoundingRects) {
816
- const { docIndex: rectDocIndex, pageIndex, rect } = this.pagesBoundingRects[key]
964
+ const pageBoundsMap = this.getPageBoundsMap()
965
+ for (const key in pageBoundsMap) {
966
+ const { docIndex: rectDocIndex, pageIndex, rect } = pageBoundsMap[key]
817
967
  if (rectDocIndex === docIndex &&
818
968
  mouseX >= rect.left && mouseX <= rect.right &&
819
969
  mouseY >= rect.top && mouseY <= rect.bottom) {
@@ -822,23 +972,25 @@ export default {
822
972
  }
823
973
  }
824
974
 
825
- const targetPageRect = this.pagesBoundingRects[`${docIndex}-${targetPageIndex}`]?.rect
975
+ const targetPageRect = this.getPageRect(docIndex, targetPageIndex)
826
976
  if (!targetPageRect) return currentPageIndex
827
977
 
828
978
  const pagesScale = this.getDisplayedPageScale(docIndex, targetPageIndex)
829
979
  const relX = (mouseX - targetPageRect.left - this.draggingElementShift.x) / pagesScale - (this.draggingInitialMouseOffset.x / pagesScale)
830
980
  const relY = (mouseY - targetPageRect.top - this.draggingElementShift.y) / pagesScale - (this.draggingInitialMouseOffset.y / pagesScale)
831
981
 
832
- const pageWidth = this.getPageWidth(docIndex, targetPageIndex)
833
- const pageHeight = this.getPageHeight(docIndex, targetPageIndex)
834
-
835
- const clampedX = Math.max(0, Math.min(relX, pageWidth - targetObject.width))
836
- const clampedY = Math.max(0, Math.min(relY, pageHeight - targetObject.height))
982
+ const { width: pageWidth, height: pageHeight } = this.getPageSize(docIndex, targetPageIndex)
983
+ const { x: clampedX, y: clampedY } = clampPosition(
984
+ relX,
985
+ relY,
986
+ targetObject.width,
987
+ targetObject.height,
988
+ pageWidth,
989
+ pageHeight,
990
+ )
837
991
 
838
992
  if (targetPageIndex !== currentPageIndex) {
839
- doc.allObjects[currentPageIndex] = doc.allObjects[currentPageIndex].filter(
840
- obj => obj.id !== objectId
841
- )
993
+ this.removeObjectFromPage(docIndex, currentPageIndex, objectId)
842
994
  doc.allObjects[targetPageIndex].push({
843
995
  ...targetObject,
844
996
  x: clampedX,
@@ -846,9 +998,7 @@ export default {
846
998
  })
847
999
  this.objectIndexCache[cacheKey] = targetPageIndex
848
1000
  } else if (clampedX !== targetObject.x || clampedY !== targetObject.y) {
849
- doc.allObjects[currentPageIndex] = doc.allObjects[currentPageIndex].map(obj =>
850
- obj.id === objectId ? { ...obj, x: clampedX, y: clampedY } : obj
851
- )
1001
+ this.updateObjectInPage(docIndex, currentPageIndex, objectId, { x: clampedX, y: clampedY })
852
1002
  }
853
1003
 
854
1004
  return targetPageIndex
@@ -856,8 +1006,9 @@ export default {
856
1006
 
857
1007
  onMeasure(e, docIndex, pageIndex) {
858
1008
  if (docIndex < 0 || docIndex >= this.pdfDocuments.length) return
859
- this.pdfDocuments[docIndex].pagesScale[pageIndex] = e.scale
860
- this.cachePageBounds()
1009
+ this.pdfDocuments[docIndex].pagesScale.splice(pageIndex, 1, e.scale)
1010
+ this._pageMeasurementCache[`${docIndex}-${pageIndex}`] = null
1011
+ this.cachePageBoundsForPage(docIndex, pageIndex)
861
1012
  if (this.autoFitZoom) {
862
1013
  this.scheduleAutoFitZoom()
863
1014
  }
@@ -871,16 +1022,26 @@ export default {
871
1022
  getPageWidth(docIndex, pageIndex) {
872
1023
  const pageRef = this.getPageComponent(docIndex, pageIndex)
873
1024
  if (!pageRef) return 0
874
- const doc = this.pdfDocuments[docIndex]
875
- const pagesScale = doc.pagesScale[pageIndex] || 1
876
- return pageRef.getCanvasMeasurement().canvasWidth / pagesScale
1025
+ const measurement = this.getCachedMeasurement(docIndex, pageIndex, pageRef)
1026
+ return measurement.width
877
1027
  },
878
1028
  getPageHeight(docIndex, pageIndex) {
879
1029
  const pageRef = this.getPageComponent(docIndex, pageIndex)
880
1030
  if (!pageRef) return 0
1031
+ const measurement = this.getCachedMeasurement(docIndex, pageIndex, pageRef)
1032
+ return measurement.height
1033
+ },
1034
+ getPageSize(docIndex, pageIndex) {
1035
+ return {
1036
+ width: this.getPageWidth(docIndex, pageIndex),
1037
+ height: this.getPageHeight(docIndex, pageIndex),
1038
+ }
1039
+ },
1040
+ getCachedMeasurement(docIndex, pageIndex, pageRef) {
1041
+ const cacheKey = `${docIndex}-${pageIndex}`
881
1042
  const doc = this.pdfDocuments[docIndex]
882
1043
  const pagesScale = doc.pagesScale[pageIndex] || 1
883
- return pageRef.getCanvasMeasurement().canvasHeight / pagesScale
1044
+ return getCachedMeasurement(this._pageMeasurementCache, cacheKey, pageRef, pagesScale)
884
1045
  },
885
1046
  calculateOptimalScale(maxPageWidth) {
886
1047
  const containerWidth = this.$el?.clientWidth || 0
@@ -924,9 +1085,8 @@ export default {
924
1085
  if (Math.abs(optimalScale - this.scale) > 0.01) {
925
1086
  this.scale = optimalScale
926
1087
  this.visualScale = optimalScale
927
- this.pdfDocuments.forEach((doc) => {
928
- doc.pagesScale = doc.pagesScale.map(() => this.scale)
929
- })
1088
+ applyScaleToDocs(this.pdfDocuments, this.scale)
1089
+ this._pageMeasurementCache = {}
930
1090
  this.cachePageBounds()
931
1091
  }
932
1092
  },
@@ -968,15 +1128,6 @@ export default {
968
1128
  opacity: 0.7;
969
1129
  pointer-events: none;
970
1130
  }
971
- .drag-ghost {
972
- position: fixed;
973
- opacity: 0.9;
974
- pointer-events: none;
975
- z-index: 10000;
976
- transform-origin: top left;
977
- transition: none;
978
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
979
- }
980
1131
  .overlay {
981
1132
  position: absolute;
982
1133
  top: 0;