|
@@ -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)
|
|
|
+ }
|
|
|
+}
|