UNPKG

@cogic/annotorious

Version:

A JavaScript image annotation library

184 lines (147 loc) 5.83 kB
import { SVG_NAMESPACE, addClass } from '../util/SVG'; /** * Parses a W3C Web Annotation FragmentSelector conforming * to the Media Fragments spec. Supports (well-formed) xywh=pixel * and xywh=percent fragments. */ export const parseRectFragment = (annotation, image) => { const selector = annotation.selector('FragmentSelector'); if (selector?.conformsTo.startsWith('http://www.w3.org/TR/media-frags')) { const { value } = selector; const format = value.includes(':') ? value.substring(value.indexOf('=') + 1, value.indexOf(':')) : 'pixel'; const coords = value.includes(':') ? value.substring(value.indexOf(':') + 1) : value.substring(value.indexOf('=') + 1); let [ x, y, w, h ] = coords.split(',').map(parseFloat); if (format.toLowerCase() === 'percent') { x = x * image.naturalWidth / 100; y = y * image.naturalHeight / 100; w = w * image.naturalWidth / 100; h = h * image.naturalHeight / 100; } return { x, y, w, h }; } } /** * Serializes a (x, y, w, h)-tuple as Media Fragment selector * using pixel coordinates. */ const toPixelRectFragment = (x, y, w, h, image) => ({ source: image?.src, selector: { type: "FragmentSelector", conformsTo: "http://www.w3.org/TR/media-frags/", value: `xywh=pixel:${x},${y},${w},${h}` } }); /** * Serializes a (x, y, w, h)-tuple as Media Fragment selector * using percent coordinates. */ const toPercentRectFragment = (x, y, w, h, image) => { const px = x / image.naturalWidth * 100; const py = y / image.naturalHeight * 100; const pw = w / image.naturalWidth * 100; const ph = h / image.naturalHeight * 100; return { source: image.src, selector: { type: "FragmentSelector", conformsTo: "http://www.w3.org/TR/media-frags/", value: `xywh=percent:${px},${py},${pw},${ph}` } } } export const toRectFragment = (x, y, w, h, image, fragmentUnit) => fragmentUnit?.toLowerCase() === 'percent' ? toPercentRectFragment(x, y, w, h, image) : toPixelRectFragment(x, y, w, h, image); /** Shorthand to apply the given (x, y, w, h) to the SVG shape **/ const setXYWH = (shape, x, y, w, h) => { shape.setAttribute('x', x); shape.setAttribute('y', y); shape.setAttribute('width', w); shape.setAttribute('height', h); } const setPointXY = (shape, x, y) => { shape.setAttribute('cx', x); shape.setAttribute('cy', y); shape.setAttribute('r', 7); // TODO make configurable } export const drawRectMask = (imageDimensions, x, y, w, h) => { const mask = document.createElementNS(SVG_NAMESPACE, 'path'); mask.setAttribute('fill-rule', 'evenodd'); const { naturalWidth, naturalHeight } = imageDimensions; mask.setAttribute('d', `M0 0 h${naturalWidth} v${naturalHeight} h-${naturalWidth} z M${x} ${y} h${w} v${h} h-${w} z`); return mask; } export const setRectMaskSize = (mask, imageDimensions, x, y, w, h) => { const { naturalWidth, naturalHeight } = imageDimensions; mask.setAttribute('d', `M0 0 h${naturalWidth} v${naturalHeight} h-${naturalWidth} z M${x} ${y} h${w} v${h} h-${w} z`); } /** * Draws an SVG rectangle, either from an annotation, or an * (x, y, w, h)-tuple. */ export const drawRect = (arg1, arg2, arg3, arg4) => { const { x, y, w, h } = arg1.type === 'Annotation' || arg1.type === 'Selection' ? parseRectFragment(arg1, arg2) : { x: arg1, y: arg2, w: arg3, h: arg4 }; const g = document.createElementNS(SVG_NAMESPACE, 'g'); if (w === 0 && h === 0) { // Edge case: rect is actually a point addClass(g, 'a9s-point'); addClass(g, 'a9s-non-scaling'); g.setAttribute('transform-origin', `${x} ${y}`); const outerPoint = document.createElementNS(SVG_NAMESPACE, 'circle'); const innerPoint = document.createElementNS(SVG_NAMESPACE, 'circle'); innerPoint.setAttribute('class', 'a9s-inner'); setPointXY(innerPoint, x, y); outerPoint.setAttribute('class', 'a9s-outer'); setPointXY(outerPoint, x, y); g.appendChild(outerPoint); g.appendChild(innerPoint); } else { const outerRect = document.createElementNS(SVG_NAMESPACE, 'rect'); const innerRect = document.createElementNS(SVG_NAMESPACE, 'rect'); innerRect.setAttribute('class', 'a9s-inner'); setXYWH(innerRect, x, y, w, h); outerRect.setAttribute('class', 'a9s-outer'); setXYWH(outerRect, x, y, w, h); g.appendChild(outerRect); g.appendChild(innerRect); } return g; } /** Gets the (x, y, w, h)-values from the attributes of the SVG group **/ export const getRectSize = g => { const outer = g.querySelector('.a9s-outer'); if (outer.nodeName === 'rect') { const x = parseFloat(outer.getAttribute('x')); const y = parseFloat(outer.getAttribute('y')); const w = parseFloat(outer.getAttribute('width')); const h = parseFloat(outer.getAttribute('height')); return { x, y, w, h }; } else { const x = parseFloat(outer.getAttribute('cx')); const y = parseFloat(outer.getAttribute('cy')); return { x, y, w: 0, h: 0 }; } } /** Applies the (x, y, w, h)-values to the rects in the SVG group **/ export const setRectSize = (g, x, y, w, h) => { const inner = g.querySelector('.a9s-inner'); const outer = g.querySelector('.a9s-outer'); if (outer.nodeName === 'rect') { setXYWH(inner, x, y, w, h); setXYWH(outer, x, y, w, h); } else { setPointXY(inner, x, y); setPointXY(outer, x, y); } } /** * Shorthand to get the area (rectangle w x h) from the * annotation's fragment selector. */ export const rectArea = (annotation, image) => { const { w, h } = parseRectFragment(annotation, image); return w * h; }