100 lines
2.7 KiB
TypeScript
100 lines
2.7 KiB
TypeScript
export type LocalPoint = readonly [number, number]
|
|
|
|
export type Frame = {
|
|
x: number
|
|
y: number
|
|
w: number
|
|
h: number
|
|
}
|
|
|
|
export const VIEW = 300
|
|
export const HALF = VIEW / 2
|
|
export const HEAD_RE = 114.2705
|
|
|
|
export const localHeadRadius = (svg: SVGSVGElement | null, radius = HEAD_RE) => {
|
|
if (!svg) return 95
|
|
const box = svg.viewBox.baseVal
|
|
if (!box.width) return 95
|
|
return (radius / box.width) * VIEW
|
|
}
|
|
|
|
export const toLocal = (
|
|
svg: SVGSVGElement | null,
|
|
clientX: number,
|
|
clientY: number
|
|
): LocalPoint | null => {
|
|
if (!svg) return null
|
|
const rectangle = svg.getBoundingClientRect()
|
|
if (rectangle.width < 1 || rectangle.height < 1) return null
|
|
return [
|
|
((clientX - rectangle.left) / rectangle.width) * VIEW - HALF,
|
|
((clientY - rectangle.top) / rectangle.height) * VIEW - HALF
|
|
]
|
|
}
|
|
|
|
export const overlayToHost = (
|
|
host: SVGSVGElement | null,
|
|
x: number,
|
|
y: number
|
|
): LocalPoint | null => {
|
|
if (!host) return null
|
|
const box = host.viewBox.baseVal
|
|
const w = box.width || VIEW
|
|
const h = box.height || VIEW
|
|
return [box.x + ((x + HALF) / VIEW) * w, box.y + ((y + HALF) / VIEW) * h]
|
|
}
|
|
|
|
export const hostToLocal = (
|
|
host: SVGSVGElement | null,
|
|
x: number,
|
|
y: number
|
|
): LocalPoint | null => {
|
|
if (!host) return null
|
|
const matrix = host.getScreenCTM()
|
|
if (!matrix) return toLocal(host, x, y)
|
|
const point = host.createSVGPoint()
|
|
point.x = x
|
|
point.y = y
|
|
const screen = point.matrixTransform(matrix)
|
|
return toLocal(host, screen.x, screen.y)
|
|
}
|
|
|
|
export const mapElementPoint = (
|
|
el: SVGGraphicsElement,
|
|
x: number,
|
|
y: number,
|
|
host: SVGSVGElement | null
|
|
): LocalPoint | null => {
|
|
const svg = el.ownerSVGElement
|
|
const screenMatrix = el.getScreenCTM()
|
|
const localMatrix = el.getCTM()
|
|
const hostMatrix = host?.getScreenCTM()
|
|
if (!svg || (!screenMatrix && (!localMatrix || !hostMatrix))) return null
|
|
const point = svg.createSVGPoint()
|
|
point.x = x
|
|
point.y = y
|
|
const screen = screenMatrix
|
|
? point.matrixTransform(screenMatrix)
|
|
: point.matrixTransform(localMatrix!).matrixTransform(hostMatrix!)
|
|
return toLocal(host ?? svg, screen.x, screen.y)
|
|
}
|
|
|
|
export const frameOf = (
|
|
el: SVGGraphicsElement | null | undefined,
|
|
overlay: SVGSVGElement | null
|
|
): Frame | null => {
|
|
if (!el || !overlay) return null
|
|
if (el.style.display === 'none') return null
|
|
const box = el.getBoundingClientRect()
|
|
const host = overlay.getBoundingClientRect()
|
|
if (box.width < 0.5 || box.height < 0.5 || host.width < 1) return null
|
|
const sx = VIEW / host.width
|
|
const sy = VIEW / host.height
|
|
return {
|
|
x: (box.left + box.width / 2 - host.left) * sx - HALF,
|
|
y: (box.top + box.height / 2 - host.top) * sy - HALF,
|
|
w: box.width * sx,
|
|
h: box.height * sy
|
|
}
|
|
}
|