forked from johnfactotum/foliate-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoverlayer.js
103 lines (101 loc) · 3.79 KB
/
overlayer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
const createSVGElement = tag =>
document.createElementNS('http://www.w3.org/2000/svg', tag)
export class Overlayer {
#svg = createSVGElement('svg')
#map = new Map()
constructor() {
Object.assign(this.#svg.style, {
position: 'absolute', top: '0', left: '0',
width: '100%', height: '100%',
pointerEvents: 'none',
})
const darkMode = matchMedia('(prefers-color-scheme: dark)')
const setBlendMode = () => this.#svg.style.mixBlendMode =
darkMode.matches ? 'normal' : 'multiply'
darkMode.addEventListener('change', setBlendMode)
setBlendMode()
}
get element() {
return this.#svg
}
add(key, range, draw, options) {
if (this.#map.has(key)) this.remove(key)
if (typeof range === 'function') range = range(this.#svg.getRootNode())
const rects = range.getClientRects()
const element = draw(rects, options)
this.#svg.append(element)
this.#map.set(key, { range, draw, options, element, rects })
}
remove(key) {
if (!this.#map.has(key)) return
this.#svg.removeChild(this.#map.get(key).element)
this.#map.delete(key)
}
redraw() {
for (const obj of this.#map.values()) {
const { range, draw, options, element } = obj
this.#svg.removeChild(element)
const rects = range.getClientRects()
const el = draw(rects, options)
this.#svg.append(el)
obj.element = el
obj.rects = rects
}
}
hitTest({ x, y }) {
const arr = Array.from(this.#map.entries())
// loop in reverse to hit more recently added items first
for (let i = arr.length - 1; i >= 0; i--) {
const [key, obj] = arr[i]
for (const { left, top, right, bottom } of obj.rects)
if (top <= y && left <= x && bottom > y && right > x)
return [key, obj.range]
}
return []
}
static underline(rects, options = {}) {
// TODO: in vertical-rl, the bōsen (sideline) should be on the right
const { color = 'red', width: strokeWidth = 2 } = options
const g = createSVGElement('g')
g.setAttribute('fill', color)
for (const { left, bottom, width } of rects) {
const el = createSVGElement('rect')
el.setAttribute('x', left)
el.setAttribute('y', bottom - strokeWidth)
el.setAttribute('height', strokeWidth)
el.setAttribute('width', width)
g.append(el)
}
return g
}
static highlight(rects, options = {}) {
const { color = 'red' } = options
const g = createSVGElement('g')
g.setAttribute('fill', color)
g.setAttribute('fill-opacity', .3)
for (const { left, top, height, width } of rects) {
const el = createSVGElement('rect')
el.setAttribute('x', left)
el.setAttribute('y', top)
el.setAttribute('height', height)
el.setAttribute('width', width)
g.append(el)
}
return g
}
// make an exact copy of an image in the overlay
// one can then apply filters to the entire element, without affecting them;
// it's a bit silly and probably better to just invert images twice
// (though the color will be off in that case if you do heu-rotate)
static copyImage([rect], options = {}) {
const { src } = options
const image = createSVGElement('image')
const { left, top, height, width } = rect
image.setAttribute('href', src)
image.setAttribute('x', left)
image.setAttribute('y', top)
image.setAttribute('height', height)
image.setAttribute('width', width)
return image
}
}