Browse Source

fix: 重构文字选中

liutian 1 year ago
parent
commit
e655cf32e4

+ 292 - 0
packages/core/src/TextSelection.ts

@@ -0,0 +1,292 @@
+// @ts-nocheck
+import { getAbsoluteCoordinate, getInitialPoint, getActualPoint } from './annotation/utils.js';
+
+const markupType = ['highlight', 'underline', 'squiggly', 'strikeout', '']
+type Point = {
+  x: number,
+  y: number
+}
+
+type PagePtr = {
+  doc: number
+  pagePtr: number
+  textPtr: number
+}
+export class TextSelection {
+  _selection: string | null
+  #pagePtr: number | null
+  #textPtr: number | null
+  messageHandler: any
+  container: HTMLDivElement | null
+  textContainer: HTMLDivElement | null
+  selected: any
+  viewport: {
+    width: number
+    height: number
+  }
+  scale: number
+  eventBus: any
+  _tool: string | null = null
+  color: string | null = null
+  selecting: boolean = false
+  pageViewer: any
+  pageIndex: number
+
+  constructor(options: {
+    viewport: {
+      width: number,
+      height: number
+    },
+    scale: number,
+    pageIndex: number,
+    container: HTMLDivElement,
+    pagePtr: PagePtr,
+    messageHandler: any,
+    eventBus: any,
+    selected: any,
+    tool: string,
+    color: string,
+    pageViewer: any
+  }) {
+    this._selection = null
+    this.messageHandler = options.messageHandler
+    this.container = options.container || null
+    this.#textPtr = options.pagePtr.textPtr
+    this.#pagePtr = options.pagePtr.pagePtr
+    this.selected = options.selected
+    this.viewport = options.viewport
+    this.scale = options.scale
+    this.eventBus = options.eventBus
+    this.pageIndex = options.pageIndex
+    this.pageViewer = options.pageViewer
+
+    this.handleTool = this.handleTool.bind(this)
+    this.handleMouseDown = this.handleMouseDown.bind(this)
+    this.handleMouseMove = this.handleMouseMove.bind(this)
+    this.handleMouseUp = this.handleMouseUp.bind(this)
+
+    this.tool = options.tool
+    this.color = options.color
+    this.eventBus._on('toolChanged', this.handleTool)
+  }
+
+  handleTool ({
+    tool,
+    color
+  }: {
+    tool: string,
+    color: string
+  }) {
+    this.tool = tool
+    this.color = color
+
+    if (markupType.includes(this.tool)) {
+      document.body.style.userSelect = 'auto'
+      document.body.style.webkitUserSelect = 'auto'
+    } else {
+      document.body.style.userSelect = 'none'
+      document.body.style.webkitUserSelect = 'none'
+    }
+  }
+
+  get tool () {
+    return this._tool
+  }
+
+  set tool (toolType) {
+    if (toolType === this._tool) return
+    this._tool = toolType
+    if (!markupType.includes(this.tool)) {
+      document.removeEventListener('mousedown', this.handleMouseDown)
+      document.removeEventListener('touchstart', this.handleMouseDown)
+      document.removeEventListener('mousemove', this.handleMouseMove)
+      document.removeEventListener('touchmove', this.handleMouseMove)
+      return
+    }
+    document.addEventListener('mousedown', this.handleMouseDown)
+    document.addEventListener('touchstart', this.handleMouseDown)
+    document.addEventListener('mousemove', this.handleMouseMove)
+    document.addEventListener('touchmove', this.handleMouseMove)
+  }
+
+  testPoint(event: MouseEvent) {
+    const { x, y, width, height } = getAbsoluteCoordinate(this.container, event)
+    if (x < 0 || y < 0 || x > width || y > height) return false
+    return {
+      x,
+      y
+    }
+  }
+
+  handleMouseDown(event: MouseEvent) {
+    this.cleanSelection()
+    const tool = this.tool
+    if (!markupType.includes(tool)) return
+    const inPage = this.testPoint(event)
+    if (!inPage) return
+
+    const { x, y } = inPage
+    const point = getInitialPoint({ x, y }, this.viewport, this.scale)
+    this.startPoint = point
+    this.selecting = true
+
+    document.addEventListener('mouseup', this.handleMouseUp)
+    document.addEventListener('touchend', this.handleMouseUp)
+  }
+
+  async handleMouseMove(event: MouseEvent) {
+    const inPage = this.testPoint(event)
+    if (!inPage) return
+    const { x, y } = inPage
+    const point = getInitialPoint({ x, y }, this.viewport, this.scale)
+
+    const isText = await this.messageHandler.sendWithPromise('GetCharIndexAtPos', {
+      pagePtr: this.#pagePtr,
+      textPtr: this.#textPtr,
+      point
+    })
+    if (isText) {
+      this.container?.classList.add('text')
+      this.endPoint = point
+    } else {
+      this.container?.classList.remove('text')
+    }
+    if (!this.selecting) return
+
+    const selection = await this.messageHandler.sendWithPromise('GetCharsRangeAtPos', {
+      pagePtr: this.#pagePtr,
+      textPtr: this.#textPtr,
+      start: this.startPoint,
+      end: this.endPoint
+    })
+    this._selection = selection
+    this.updateSelection()
+  }
+
+  handleMouseUp() {
+    const markupType = ['highlight', 'underline', 'squiggly', 'strikeout']
+    if (this.selecting && this._selection && markupType.includes(this.tool)) {
+      const annotationData = {
+        operate: "add-annot",
+        type: this.tool,
+        pageIndex: this.pageIndex,
+        createDate: new Date(),
+        transparency: 0.5,
+        quadPoints: this.quadPoints,
+        rect: this.rect,
+        color: this.color,
+        content: this._selection.textContent || undefined
+      }
+      if (this.tool === 'highlight') {
+        annotationData.transparency = 0.5
+      }
+      this.eventBus.dispatch('annotationChange', {
+        type: 'add',
+        annotation: annotationData
+      })
+      this.cleanSelection()
+      const pageView = this.pageViewer
+      if (pageView && pageView.compdfAnnotationLayer) {
+        pageView.compdfAnnotationLayer.renderAnnotation(annotationData)
+      }
+    }
+    this.startPoint = null
+    this.endPoint = null
+    this.selecting = false
+    this._selection = null
+    document.removeEventListener('mouseup', this.handleMouseUp)
+    document.removeEventListener('touchend', this.handleMouseUp)
+  }
+
+  updateSelection() {
+    const textRects = this._selection.textRects
+    this.textContainer?.textContent = ''
+    const quadPoints = []
+    const topArray = []
+    const bottomArray = []
+    const leftArray = []
+    const rightArray = []
+    for (let i = 0; i < textRects.length; i++) {
+      const textRect = textRects[i]
+      const { Left: left, Top: top, Right: right, Bottom: bottom } = textRect
+      const rect = {
+        left: left * this.scale,
+        top: top * this.scale,
+        right: right * this.scale,
+        bottom: bottom * this.scale
+      }
+      topArray.push(top)
+      bottomArray.push(bottom)
+      leftArray.push(left)
+      rightArray.push(right)
+      const leftTop = {
+        PointX: left,
+        PointY: top
+      }
+      const rightTop = {
+        PointX: right,
+        PointY: top
+      }
+      const leftBottom = {
+        PointX: left,
+        PointY: bottom
+      }
+      const rightBottom = {
+        PointX: right,
+        PointY: bottom
+      }
+      quadPoints.push(leftTop, rightTop, leftBottom, rightBottom)
+      this.drawSelection(rect)
+    }
+    this.quadPoints = quadPoints
+    const top = Math.min(...topArray)
+    const bottom = Math.max(...bottomArray)
+    const left = Math.min(...leftArray)
+    const right = Math.max(...rightArray)
+    this.rect = {
+      left,
+      top,
+      right,
+      bottom
+    }
+  }
+
+  drawSelection(rect: {
+    left: number,
+    top: number,
+    right: number,
+    bottom: number
+  }) {
+    if (!this.textContainer) {
+      this.textContainer = document.createElement('div')
+      this.textContainer.classList.add('text-container')
+      this.container?.appendChild(this.textContainer)
+    }
+    const { left, top, right, bottom } = rect
+    const selection = document.createElement('div')
+    selection.classList.add('text-selection')
+    selection.style.left = `${left}px`
+    selection.style.top = `${top}px`
+    selection.style.width = `${right - left}px`
+    selection.style.height = `${bottom - top}px`
+    this.textContainer?.appendChild(selection)
+  }
+
+  cleanSelection() {
+    this.textContainer?.remove()
+    this.textContainer = null
+    this._selection = null
+  }
+
+  destroy() {
+    this.textContainer?.remove()
+    this.textContainer = null
+    this._selection = null
+    document.removeEventListener('mousedown', this.handleMouseDown)
+    document.removeEventListener('touchstart', this.handleMouseDown)
+    document.removeEventListener('mousemove', this.handleMouseMove)
+    document.removeEventListener('touchmove', this.handleMouseMove)
+    document.removeEventListener('mouseup', this.handleMouseUp)
+    document.removeEventListener('touchend', this.handleMouseUp)
+  }
+}

+ 2 - 0
packages/core/src/annotation/utils.js

@@ -26,6 +26,8 @@ export function getAbsoluteCoordinate (ele, event) {
   const coordinate = {
     x: pageX - rect.left - offsetX,
     y: pageY - rect.top - offsetY,
+    width: rect.width - offsetX,
+    height: rect.height - offsetY
   };
 
   return coordinate

+ 0 - 1
packages/core/src/editor/text_editor.js

@@ -1,6 +1,5 @@
 import { getActualPoint, getClickPoint, createSvg, createElement } from '../annotation/utils';
 import { onClickOutside, setCss, isMobileDevice } from '../ui_utils';
-import { MARGIN_DISTANCE } from '../../constants';
 import copy from 'copy-to-clipboard';
 
 export class TextEditor {

+ 23 - 0
packages/core/src/pdf_page_view.js

@@ -23,6 +23,7 @@ import { PDFAnnotationLayer } from './pdf_annotation_layer.js'
 import { TextLayerBuilder } from "./text_layer_builder.js";
 import ComPDFAnnotationLayer from './annotation/layer.js';
 import { ContentContainer } from "./editor/content_container.js";
+import { TextSelection } from "./TextSelection";
 
 import AnnotationManager from "./annotations";
 import { v4 as uuidv4 } from 'uuid';
@@ -151,6 +152,7 @@ class PDFPageView {
     this.xfaLayer = null;
     this.structTreeLayer = null;
     this.contentContainer = null;
+    this.textSelection = null
 
     const div = document.createElement("div");
     div.className = "page page-number" + this.id;
@@ -802,6 +804,11 @@ class PDFPageView {
       this.contentContainer.cancel();
       this.contentContainer = null;
     }
+
+    if (this.textSelection) {
+      this.textSelection.destroy()
+      this.textSelection = null
+    }
   }
 
   cssTransform({
@@ -1053,6 +1060,22 @@ class PDFPageView {
         if (this.mode === 'editor') {
           await this.contentContainer.render()
         }
+
+        if (!this.textSelection) {
+          this.textSelection = new TextSelection({
+            viewport: this.viewport,
+            scale: this.scale,
+            pageIndex: this.pageIndex,
+            container: div,
+            eventBus: this.eventBus,
+            selected: this.selected,
+            pagePtr: this.pagesPtr[this.pageIndex],
+            messageHandler: this.messageHandler,
+            tool: this.tool,
+            color: this.color,
+            pageViewer: this
+          })
+        }
       },
       function (reason) {
         return finishPaintTask(reason);

+ 23 - 1
packages/core/src/worker/compdfkit_worker.js

@@ -23,10 +23,11 @@ let RectArray = []
 let CursorPoints = {}
 let EditTextStyle = {}
 let U8StringData = ''
+let PDFRange = {}
+let TextRectArray = []
 
 import MessageHandler from "../message_handler"
 import { convertFileToBuffer } from '../fileHandler';
-import { parse } from 'uuid';
 
 async function sleep(delay) {
   await new Promise((resolve) => setTimeout(resolve, delay))
@@ -305,6 +306,27 @@ class CPDFWorker {
       console.log(res)
     })
 
+    messageHandler.on('GetCharIndexAtPos', (data) => {
+      const { pagePtr, textPtr, point } = data
+      const { x, y } = point
+      const index = Module._GetCharIndexAtPos(pagePtr, textPtr, x, y, 5, 5)
+      return index !== -1
+    })
+
+    messageHandler.on('GetCharsRangeAtPos', (data) => {
+      const { pagePtr, textPtr, start, end } = data
+      PDFRange = {}
+      Module._GetCharsRangeAtPos(pagePtr, textPtr, start.x, start.y, end.x, end.y, 5, 5)
+      const rawTextContent = Module._GetTextContent(textPtr, PDFRange.Location, PDFRange.Length)
+      const textContent = UTF8ToString(rawTextContent)
+      TextRectArray = []
+      Module._GetRects(pagePtr, textPtr, PDFRange.Location, PDFRange.Length)
+      return {
+        textContent,
+        textRects: TextRectArray
+      }
+    })
+
     messageHandler.on('XFDFExportAnnotations', (data) => {
       const { doc } = data
       const xfdfStream = exportXFDFByStream(doc)

+ 10 - 1
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -432,6 +432,15 @@ onMounted(async () => {
     line-height: 16px;
     color: #333333;
   }
+  &.text svg {
+    cursor: text;
+  }
+  .text-container .text-selection {
+    position: absolute;
+    z-index: 122;
+    cursor: text;
+    background-color: rgba(0, 0, 255, 0.25);
+  }
 }
 
 .document.scrollHorizontal,
@@ -536,7 +545,7 @@ onMounted(async () => {
   color: transparent;
   position: absolute;
   white-space: pre;
-  pointer-events: auto;
+  pointer-events: none;
   cursor: text;
   -webkit-transform-origin: 0% 0%;
   transform-origin: 0% 0%;