Просмотр исходного кода

update: markup注释改为canvas绘制;添加数字签名增加loading;

wzl 4 недель назад
Родитель
Сommit
a6f3bb494e

+ 4 - 2
packages/core/src/annotation/layer.js

@@ -395,7 +395,8 @@ class ComPDFAnnotationLayer {
             scale: this.scale,
             eventBus: this.eventBus,
             layer: this,
-            enableReply
+            enableReply,
+            pageViewer: this.pageViewer
           })
           this.annotationsArray.push(markup)
         } else if (annotation.type === 'line' && !annotation.isDelete) {
@@ -718,7 +719,8 @@ class ComPDFAnnotationLayer {
         scale: this.scale,
         eventBus: this.eventBus,
         layer: this,
-        enableReply
+        enableReply,
+        pageViewer: this.pageViewer
       })
       this.annotationsArray.push(markup)
     } else if (annotation.type === 'line' || annotation.type === 'arrow') {

+ 2 - 2
packages/core/src/index.js

@@ -3013,7 +3013,7 @@ class ComPDFKitViewer {
     return targetObj
   }
 
-  handleSign(data) {
+  async handleSign(data) {
     const { type = 0, flag, param } = data
 
     if (flag === 'create' && !this.Signatures) {
@@ -3025,7 +3025,7 @@ class ComPDFKitViewer {
     }
     if (flag === 'save' && type) {
       const { pkcs12Buffer, password } = data
-      this.updateSignatureAp({
+      await this.updateSignatureAp({
         type,
         annotPtr: param.annotation.annotPtr,
         pkcs12Buffer,

+ 53 - 104
packages/core/src/markup/text_annotation.js

@@ -1,8 +1,7 @@
 import BaseAnnotation from '../annotation/base'
-import { hexToRgb, onClickOutside } from '../ui_utils'
+import { onClickOutside, convertColorToRGB } from '../ui_utils'
 import { createSvg, createElement } from '../annotation/utils'
 import { getActualPoint } from '../annotation/utils'
-import Color from '../color';
 
 class TextAnnotation extends BaseAnnotation {
   constructor ({
@@ -14,7 +13,8 @@ class TextAnnotation extends BaseAnnotation {
     eventBus,
     layer,
     show = false,
-    enableReply = true
+    enableReply = true,
+    pageViewer
   }) {
     super({
       container,
@@ -27,9 +27,13 @@ class TextAnnotation extends BaseAnnotation {
     })
     this.enableReply = enableReply
     this.layer = layer
+    this.pageViewer = pageViewer
     this.hidden = true
     this.markupContainer = null
     this.outerLineContainer = null
+    this.ratio = window.devicePixelRatio || 1
+    this.ctx = this.pageViewer.canvas.getContext('2d', { willReadFrequently: true })
+    this.lineWidth = 2 * this.scale * this.ratio
 
     this.onHandleClick = this.handleClick.bind(this)
     this.onDelete = this.handleDelete.bind(this)
@@ -48,6 +52,8 @@ class TextAnnotation extends BaseAnnotation {
     )
 
     const rect = this.calculate(start, end)
+    this.rect = rect
+    this.baseImageData = this.ctx.getImageData(rect.left * this.ratio, rect.top * this.ratio, Math.round(rect.width * this.ratio), Math.round(rect.height * this.ratio + this.lineWidth + 2))
 
     const quadPoints = annotation.quadPoints
     if (!quadPoints) return
@@ -72,14 +78,11 @@ class TextAnnotation extends BaseAnnotation {
       })
     }
 
-    if (annotation.type === 'highlight') {
-      this.renderHighlight(positionArray)
-    } else if (annotation.type === 'underline') {
-      this.renderUnderline(positionArray)
+    this.ctx.globalCompositeOperation = 'multiply'
+    if (['highlight', 'underline', 'strikeout'].includes(annotation.type)) {
+      this.renderCanvansRect(positionArray)
     } else if (annotation.type === 'squiggly') {
       this.renderSquiggly(positionArray)
-    } else if (annotation.type === 'strikeout') {
-      this.renderStrikeout(positionArray)
     }
     this.markupContainer.addEventListener('click', this.onHandleClick)
     
@@ -254,6 +257,11 @@ class TextAnnotation extends BaseAnnotation {
     }
     this.handleOutside()
     this.markupContainer.remove()
+
+    this.ctx.clearRect(this.rect.left * this.ratio, this.rect.top * this.ratio, this.rect.width * this.ratio, this.rect.height * this.ratio)
+    this.ctx.putImageData(this.baseImageData, this.rect.left * this.ratio, this.rect.top * this.ratio)
+    this.baseImageData = null
+
     this.annotation.isDelete = true
     const annotationData = {
       type: 'delete',
@@ -279,47 +287,35 @@ class TextAnnotation extends BaseAnnotation {
     }
   }
 
-  renderHighlight (positionArray) {
+  renderCanvansRect (positionArray) {
     const annotation = this.annotation
-    const markupContainer = document.createElement('div')
-    markupContainer.className = 'markup'
-    for (let i = 0; i < positionArray.length; i++) {
-      const currentPositionArray = positionArray[i]
-      const container = document.createElement('div')
-      this.setCss(container, {
-        position: 'absolute',
-        opacity: 0.5,
-        top: currentPositionArray.top + 'px',
-        left: currentPositionArray.left + 'px',
-        width: currentPositionArray.width + 'px',
-        height: currentPositionArray.height + 'px',
-        backgroundColor: annotation.color
-      })
-      markupContainer.append(container)
-    }
-    this.container.append(markupContainer)
-    this.markupContainer = markupContainer
-  }
+    const { R, G, B } = convertColorToRGB(annotation.color)
+    this.ctx.fillStyle = `rgba(${R}, ${G}, ${B}, ${annotation.opacity || 1})`
 
-  renderUnderline (positionArray) {
-    const annotation = this.annotation
     const markupContainer = document.createElement('div')
     markupContainer.className = 'markup'
+
     for (let i = 0; i < positionArray.length; i++) {
-      const currentPositionArray = positionArray[i]
+      const position = positionArray[i]
       const container = document.createElement('div')
       this.setCss(container, {
         position: 'absolute',
-        mixBlendMode: 'multiply',
-        opacity: annotation.opacity || 1,
-        top: currentPositionArray.top + 'px',
-        left: currentPositionArray.left + 'px',
-        width: currentPositionArray.width + 'px',
-        height: currentPositionArray.height + 'px',
-        borderBottom: '2px solid ' + annotation.color
+        top: position.top + 'px',
+        left: position.left + 'px',
+        width: position.width + 'px',
+        height: position.height + 'px'
       })
       markupContainer.append(container)
+
+      if (annotation.type === 'highlight') {
+        this.ctx.fillRect(position.left * this.ratio, position.top * this.ratio, position.width * this.ratio, position.height * this.ratio)
+      } else if (annotation.type === 'underline') {
+        this.ctx.fillRect(position.left * this.ratio, (position.top + position.height) * this.ratio, position.width * this.ratio, this.lineWidth)
+      } else if (annotation.type === 'strikeout') {
+        this.ctx.fillRect(position.left * this.ratio, (position.top + (position.height / 2)) * this.ratio, position.width * this.ratio, this.lineWidth)
+      }
     }
+    
     this.container.append(markupContainer)
     this.markupContainer = markupContainer
   }
@@ -328,80 +324,33 @@ class TextAnnotation extends BaseAnnotation {
     const annotation = this.annotation
     const markupContainer = document.createElement('div')
     markupContainer.className = 'markup'
+
+    const amplitude = 4 * this.scale  // 波动幅度
+    const wavelength = 42 * this.scale // 波长
+    const { R, G, B } = convertColorToRGB(annotation.color)
+    this.ctx.strokeStyle = `rgba(${R}, ${G}, ${B}, ${annotation.opacity || 1})`
+    this.ctx.lineWidth = this.lineWidth
+
     for (let i = 0; i < positionArray.length; i++) {
-      const currentPositionArray = positionArray[i]
+      const position = positionArray[i]
       const container = document.createElement('div')
       this.setCss(container, {
         position: 'absolute',
-        mixBlendMode: 'multiply',
         overflow: 'hidden',
         opacity: annotation.opacity || 1,
-        top: currentPositionArray.top + 'px',
-        left: currentPositionArray.left + 'px',
-        width: currentPositionArray.width + 'px',
-        height: currentPositionArray.height + 'px',
+        top: position.top + 'px',
+        left: position.left + 'px',
+        width: position.width + 'px',
+        height: position.height + 'px',
       })
-      
-      const squigglyBefore = document.createElement('div')
-      this.setCss(squigglyBefore, {
-        position: 'absolute',
-        width: '100%',
-        height: '5px',
-        left: '0px',
-        bottom: '1px',
-        background: `radial-gradient(ellipse, transparent, transparent 8px, ${annotation.color} 9px, ${annotation.color} 10px, transparent 11px)`,
-        backgroundSize: '22px 26px',
-        backgroundRepeat: 'repeat-x'
-      })
-      const squigglyAfter = document.createElement('div')
-      this.setCss(squigglyAfter, {
-        position: 'absolute',
-        width: '100%',
-        height: '5px',
-        left: '11px',
-        bottom: '-2px',
-        background: `radial-gradient(ellipse, transparent, transparent 8px, ${annotation.color} 9px, ${annotation.color} 10px, transparent 11px)`,
-        backgroundSize: '22px 26px',
-        backgroundPosition: '0px -22px',
-        backgroundRepeat: 'repeat-x'
-      })
-      container.append(squigglyBefore)
-      container.append(squigglyAfter)
       markupContainer.append(container)
-    }
-    this.container.append(markupContainer)
-    this.markupContainer = markupContainer
-  }
 
-  renderStrikeout (positionArray) {
-    const annotation = this.annotation
-    const markupContainer = document.createElement('div')
-    markupContainer.className = 'markup'
-    for (let i = 0; i < positionArray.length; i++) {
-      const currentPositionArray = positionArray[i]
-      const container = document.createElement('div')
-      this.setCss(container, {
-        position: 'absolute',
-        mixBlendMode: 'multiply',
-        opacity: annotation.opacity || 1,
-        top: currentPositionArray.top + 'px',
-        left: currentPositionArray.left + 'px',
-        width: currentPositionArray.width + 'px',
-        height: currentPositionArray.height + 'px'
-      })
-      const strikeout = document.createElement('div')
-      strikeout.classList.add('strikeout')
-      this.setCss(strikeout, {
-        position: 'absolute',
-        opacity: 0.8,
-        left: 0,
-        width: '100%',
-        height: '2px',
-        transform: 'translateY(-50%)',
-        backgroundColor: annotation.color
-      })
-      container.append(strikeout)
-      markupContainer.append(container)
+      this.ctx.beginPath()
+      for (let x = position.left * this.ratio; x <= (position.left + position.width) * this.ratio; x++) {
+        const y = (position.top + position.height) * this.ratio + amplitude * Math.sin((x / wavelength) * 2 * Math.PI)
+        this.ctx.lineTo(x, y)
+      }
+      this.ctx.stroke()
     }
     this.container.append(markupContainer)
     this.markupContainer = markupContainer

+ 3 - 2
packages/webview/src/components/Dialogs/AddDigitalFileDialog.vue

@@ -65,8 +65,10 @@ const closeDialog = (flag) => {
 }
 
 const next = async () => {
+  if (!inputFile.value) return
   const result = await validatePwd()
   if (!result) return
+  useViewer.setCommonLoading(true)
 
   const subject = result.subject
 
@@ -93,13 +95,12 @@ const next = async () => {
   })
   useViewer.setCertSubject(subject)
   useViewer.setDigitalSignaturePreview(src)
-
+  useViewer.setCommonLoading(false)
   closeDialog()
   useViewer.openElement('signatureAppearanceDialog')
 }
 
 const validatePwd = async () => {
-  if (!inputFile.value) return
   const fileReader = new FileReader()
   const arrayBufferPromise = new Promise((resolve, reject) => {
     fileReader.addEventListener('load', async (e) => {

+ 4 - 2
packages/webview/src/components/Dialogs/SignatureAppearanceDialog.vue

@@ -341,11 +341,12 @@ const handleFile = async (evt) => {
   await getPreview(content)
 }
 
-const save = () => {
+const save = async () => {
+  useViewer.setCommonLoading(true)
   const content = getContent()
   const params = getParams(content)
   const pkcs12 = useDocument.getPkcs12
-  core.handleSign({
+  await core.handleSign({
     type: 1,
     flag: 'save',
     param: {
@@ -356,6 +357,7 @@ const save = () => {
     password: pkcs12.password
   })
   closePanel()
+  useViewer.setCommonLoading(false)
 }
 
 const openDrawPanel = () => {

+ 4 - 0
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -65,6 +65,9 @@
   <AddDigitalFileDialog />
   <RedactionConfirmDialog />
   <div v-if="loading && loadingPercent < 100" class="loading-state">{{ $t('loading') }}...</div>
+  <div class="mask" :class="{ active: commonLoading }">
+    <div v-show="commonLoading" class="loading"><Loading /></div>
+  </div>
   <div v-show="!load && activePanelTab !== 'COMPARISON' && ['compare', 'view'].includes(toolMode)" class="upload-container">
     <input id="fileInput" type="file" accept=".pdf" @change="handleUpload" name="document"/>
     <label for="fileInput">{{ $t('upload') }}</label>
@@ -121,6 +124,7 @@ const isPageNavOverlayDisabled = computed(() => useViewer.getDisabledElements('p
 const activeTool = computed(() => useDocument.getActiveTool)
 const scaleValue = computed(() => useViewer.getScale)
 const signatureVerify = computed(() => useViewer.getSignatureVerify)
+const commonLoading = computed(() => useViewer.getCommonLoading)
 
 let originScale = 100
 let lastClickTime = 0 // 用于手动检测双击

+ 3 - 8
packages/webview/src/components/SecurityToolbar/RedactionConfirmDialog.vue

@@ -17,13 +17,10 @@
       </template>
     </Dialog>
   </div>
-  <div class="mask" :class="{ active: loading }">
-    <div v-show="loading" class="loading"><Loading /></div>
-  </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+import { computed } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 import { useDocumentStore } from '@/stores/modules/document'
 import core from '@/core'
@@ -34,20 +31,18 @@ const useDocument = useDocumentStore()
 const dialogType = computed(() => useViewer.getRedactionConfirmDialog)
 const show = computed(() => dialogType.value >= 0)
 
-const loading = ref(false)
-
 const closeDialog = () => {
   useViewer.setRedactionConfirmDialog(-1)
 }
 
 const confirm = async () => {
   const flag = dialogType.value === 1 ? 'all' : null
-  loading.value = true
+  useViewer.setCommonLoading(true)
   closeDialog()
   await core.applyRedactions(flag)
   useDocument.setToolState('')
   useViewer.setActiveToolMode('view')
-  loading.value = false
+  useViewer.setCommonLoading(false)
 }
 </script>
 

+ 7 - 0
packages/webview/src/stores/modules/viewer.js

@@ -442,6 +442,7 @@ export const useViewerStore = defineStore({
     isDisabledHeader: false,
     rightPanelButtonDisabled: true,
     redactionConfirmDialog: -1, // 0: exit, 1: apply
+    commonLoading: false,
   }),
   getters: {
     getLanguage () {
@@ -570,6 +571,9 @@ export const useViewerStore = defineStore({
     getRedactionConfirmDialog () {
       return this.redactionConfirmDialog
     },
+    getCommonLoading () {
+      return this.commonLoading
+    },
   },
   actions: {
     resetSetting () {
@@ -797,5 +801,8 @@ export const useViewerStore = defineStore({
     setRedactionConfirmDialog (val) {
       this.redactionConfirmDialog = val
     },
+    setCommonLoading (val) {
+      this.commonLoading = Boolean(val)
+    },
   }
 })