|
@@ -1,8 +1,7 @@
|
|
import BaseAnnotation from '../annotation/base'
|
|
import BaseAnnotation from '../annotation/base'
|
|
-import { hexToRgb, onClickOutside } from '../ui_utils'
|
|
|
|
|
|
+import { onClickOutside, convertColorToRGB } from '../ui_utils'
|
|
import { createSvg, createElement } from '../annotation/utils'
|
|
import { createSvg, createElement } from '../annotation/utils'
|
|
import { getActualPoint } from '../annotation/utils'
|
|
import { getActualPoint } from '../annotation/utils'
|
|
-import Color from '../color';
|
|
|
|
|
|
|
|
class TextAnnotation extends BaseAnnotation {
|
|
class TextAnnotation extends BaseAnnotation {
|
|
constructor ({
|
|
constructor ({
|
|
@@ -14,7 +13,8 @@ class TextAnnotation extends BaseAnnotation {
|
|
eventBus,
|
|
eventBus,
|
|
layer,
|
|
layer,
|
|
show = false,
|
|
show = false,
|
|
- enableReply = true
|
|
|
|
|
|
+ enableReply = true,
|
|
|
|
+ pageViewer
|
|
}) {
|
|
}) {
|
|
super({
|
|
super({
|
|
container,
|
|
container,
|
|
@@ -27,9 +27,13 @@ class TextAnnotation extends BaseAnnotation {
|
|
})
|
|
})
|
|
this.enableReply = enableReply
|
|
this.enableReply = enableReply
|
|
this.layer = layer
|
|
this.layer = layer
|
|
|
|
+ this.pageViewer = pageViewer
|
|
this.hidden = true
|
|
this.hidden = true
|
|
this.markupContainer = null
|
|
this.markupContainer = null
|
|
this.outerLineContainer = 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.onHandleClick = this.handleClick.bind(this)
|
|
this.onDelete = this.handleDelete.bind(this)
|
|
this.onDelete = this.handleDelete.bind(this)
|
|
@@ -48,6 +52,8 @@ class TextAnnotation extends BaseAnnotation {
|
|
)
|
|
)
|
|
|
|
|
|
const rect = this.calculate(start, end)
|
|
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
|
|
const quadPoints = annotation.quadPoints
|
|
if (!quadPoints) return
|
|
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') {
|
|
} else if (annotation.type === 'squiggly') {
|
|
this.renderSquiggly(positionArray)
|
|
this.renderSquiggly(positionArray)
|
|
- } else if (annotation.type === 'strikeout') {
|
|
|
|
- this.renderStrikeout(positionArray)
|
|
|
|
}
|
|
}
|
|
this.markupContainer.addEventListener('click', this.onHandleClick)
|
|
this.markupContainer.addEventListener('click', this.onHandleClick)
|
|
|
|
|
|
@@ -254,6 +257,11 @@ class TextAnnotation extends BaseAnnotation {
|
|
}
|
|
}
|
|
this.handleOutside()
|
|
this.handleOutside()
|
|
this.markupContainer.remove()
|
|
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
|
|
this.annotation.isDelete = true
|
|
const annotationData = {
|
|
const annotationData = {
|
|
type: 'delete',
|
|
type: 'delete',
|
|
@@ -279,47 +287,35 @@ class TextAnnotation extends BaseAnnotation {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- renderHighlight (positionArray) {
|
|
|
|
|
|
+ renderCanvansRect (positionArray) {
|
|
const annotation = this.annotation
|
|
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')
|
|
const markupContainer = document.createElement('div')
|
|
markupContainer.className = 'markup'
|
|
markupContainer.className = 'markup'
|
|
|
|
+
|
|
for (let i = 0; i < positionArray.length; i++) {
|
|
for (let i = 0; i < positionArray.length; i++) {
|
|
- const currentPositionArray = positionArray[i]
|
|
|
|
|
|
+ const position = positionArray[i]
|
|
const container = document.createElement('div')
|
|
const container = document.createElement('div')
|
|
this.setCss(container, {
|
|
this.setCss(container, {
|
|
position: 'absolute',
|
|
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)
|
|
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.container.append(markupContainer)
|
|
this.markupContainer = markupContainer
|
|
this.markupContainer = markupContainer
|
|
}
|
|
}
|
|
@@ -328,80 +324,33 @@ class TextAnnotation extends BaseAnnotation {
|
|
const annotation = this.annotation
|
|
const annotation = this.annotation
|
|
const markupContainer = document.createElement('div')
|
|
const markupContainer = document.createElement('div')
|
|
markupContainer.className = 'markup'
|
|
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++) {
|
|
for (let i = 0; i < positionArray.length; i++) {
|
|
- const currentPositionArray = positionArray[i]
|
|
|
|
|
|
+ const position = positionArray[i]
|
|
const container = document.createElement('div')
|
|
const container = document.createElement('div')
|
|
this.setCss(container, {
|
|
this.setCss(container, {
|
|
position: 'absolute',
|
|
position: 'absolute',
|
|
- mixBlendMode: 'multiply',
|
|
|
|
overflow: 'hidden',
|
|
overflow: 'hidden',
|
|
opacity: annotation.opacity || 1,
|
|
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)
|
|
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.container.append(markupContainer)
|
|
this.markupContainer = markupContainer
|
|
this.markupContainer = markupContainer
|