lichen-annotate-pdf
Version:
Annotation layer for pdf.js in vue fork of Aaron Leong pdf-annotate-vue
277 lines (237 loc) • 7.69 kB
JavaScript
import createStyleSheet from 'create-stylesheet';
import { getTranslation } from '../render/appendChild';
import {
applyTransform,
applyInverseTransform,
translate,
rotate,
scale
} from '../utils/mathUtils';
export const BORDER_COLOR = '#00BFFF';
const userSelectStyleSheet = createStyleSheet({
body: {
'-webkit-user-select': 'none',
'-moz-user-select': 'none',
'-ms-user-select': 'none',
'user-select': 'none'
}
});
userSelectStyleSheet.setAttribute('data-pdf-annotate-user-select', 'true');
/**
* Find the SVGElement that contains all the annotations for a page
*
* @param {Element} node An annotation within that container
* @return {SVGElement} The container SVG or null if it can't be found
*/
export function findSVGContainer(node) {
let parentNode = node;
while ((parentNode = parentNode.parentNode) &&
parentNode !== document) {
if (parentNode.nodeName.toUpperCase() === 'SVG' &&
parentNode.getAttribute('data-pdf-annotate-container') === 'true') {
return parentNode;
}
}
return null;
}
/**
* Find an SVGElement container at a given point
*
* @param {Number} x The x coordinate of the point
* @param {Number} y The y coordinate of the point
* @return {SVGElement} The container SVG or null if one can't be found
*/
export function findSVGAtPoint(x, y) {
let elements = document.querySelectorAll('svg[data-pdf-annotate-container="true"]');
for (let i = 0, l = elements.length; i < l; i++) {
let el = elements[i];
let rect = el.getBoundingClientRect();
if (pointIntersectsRect(x, y, rect)) {
return el;
}
}
return null;
}
/**
* Find an Element that represents an annotation at a given point.
*
* IMPORTANT: Requires the annotation layer to be the top most element so
* either use z-ordering or make it the leaf container.
*
* @param {Number} x The x coordinate of the point
* @param {Number} y The y coordinate of the point
* @return {Element} The annotation element or null if one can't be found
*/
export function findAnnotationAtPoint(x, y) {
let el = null;
let candidate = document.elementFromPoint(x, y);
while (!el && candidate && candidate !== document) {
let type = candidate.getAttribute('data-pdf-annotate-type');
if (type) {
el = candidate;
}
candidate = candidate.parentNode;
}
return el;
}
/**
* Determine if a point intersects a rect
*
* @param {Number} x The x coordinate of the point
* @param {Number} y The y coordinate of the point
* @param {Object} rect The points of a rect (likely from getBoundingClientRect)
* @return {Boolean} True if a collision occurs, otherwise false
*/
export function pointIntersectsRect(x, y, rect) {
return y >= rect.top && y <= rect.bottom && x >= rect.left && x <= rect.right;
}
/**
* Get the rect of an annotation element accounting for offset.
*
* @param {Element} el The element to get the rect of
* @return {Object} The dimensions of the element
*/
export function getOffsetAnnotationRect(el) {
let rect = el.getBoundingClientRect();
let { width, height } = rect;
let extraOffsetWidth = 0;
let extraOffsetHeight = 0;
if (['line', 'path'].indexOf(el.tagName.toLowerCase()) > -1 && el.getBBox) {
let bbox = el.getBBox();
extraOffsetWidth = (rect.width - bbox.width) / 2;
extraOffsetHeight = (rect.height - bbox.height) / 2;
width = bbox.width;
height = bbox.height;
}
let { offsetLeft, offsetTop } = getOffset(el);
return {
top: rect.top - offsetTop + extraOffsetHeight,
left: rect.left - offsetLeft + extraOffsetWidth,
bottom: rect.bottom - offsetTop - extraOffsetHeight,
right: rect.right - offsetLeft - extraOffsetWidth,
width: width,
height: height
};
}
/**
* Adjust scale from normalized scale (100%) to rendered scale.
*
* @param {SVGElement} svg The SVG to gather metadata from
* @param {Object} rect A map of numeric values to scale
* @return {Object} A copy of `rect` with values scaled up
*/
export function scaleUp(svg, rect) {
let result = {};
let { viewport } = getMetadata(svg);
Object.keys(rect).forEach((key) => {
result[key] = rect[key] * viewport.scale;
});
return result;
}
export function convertToSvgRect(rect, svg, viewport) {
let pt1 = [rect.x, rect.y];
let pt2 = [rect.x + rect.width, rect.y + rect.height];
pt1 = convertToSvgPoint(pt1, svg, viewport);
pt2 = convertToSvgPoint(pt2, svg, viewport);
return {
x: Math.min(pt1[0], pt2[0]),
y: Math.min(pt1[1], pt2[1]),
width: Math.abs(pt2[0] - pt1[0]),
height: Math.abs(pt2[1] - pt1[1])
};
}
export function convertToSvgPoint(pt, svg, viewport) {
viewport = viewport || getMetadata(svg).viewport;
let xform = [ 1, 0, 0, 1, 0, 0 ];
xform = scale(xform, viewport.scale, viewport.scale);
xform = rotate(xform, viewport.rotation);
let offset = getTranslation(viewport);
xform = translate(xform, offset.x, offset.y);
return applyInverseTransform(pt, xform);
}
export function convertToScreenPoint(pt, svg, viewport) {
viewport = viewport || getMetadata(svg).viewport;
let xform = [ 1, 0, 0, 1, 0, 0 ];
xform = scale(xform, viewport.scale, viewport.scale);
xform = rotate(xform, viewport.rotation);
let offset = getTranslation(viewport);
xform = translate(xform, offset.x, offset.y);
return applyTransform(pt, xform);
}
/**
* Adjust scale from rendered scale to a normalized scale (100%).
*
* @param {SVGElement} svg The SVG to gather metadata from
* @param {Object} rect A map of numeric values to scale
* @return {Object} A copy of `rect` with values scaled down
*/
export function scaleDown(svg, rect) {
let result = {};
let { viewport } = getMetadata(svg);
Object.keys(rect).forEach((key) => {
result[key] = rect[key] / viewport.scale;
});
return result;
}
/**
* Get the scroll position of an element, accounting for parent elements
*
* @param {Element} el The element to get the scroll position for
* @return {Object} The scrollTop and scrollLeft position
*/
export function getScroll(el) {
let scrollTop = 0;
let scrollLeft = 0;
let parentNode = el;
while ((parentNode = parentNode.parentNode) &&
parentNode !== document) {
scrollTop += parentNode.scrollTop;
scrollLeft += parentNode.scrollLeft;
}
return { scrollTop, scrollLeft };
}
/**
* Get the offset position of an element, accounting for parent elements
*
* @param {Element} el The element to get the offset position for
* @return {Object} The offsetTop and offsetLeft position
*/
export function getOffset(el) {
let parentNode = el;
while ((parentNode = parentNode.parentNode) &&
parentNode !== document) {
if (parentNode.nodeName.toUpperCase() === 'SVG') {
break;
}
}
let rect = parentNode.getBoundingClientRect();
return { offsetLeft: rect.left, offsetTop: rect.top };
}
/**
* Disable user ability to select text on page
*/
export function disableUserSelect() {
if (!userSelectStyleSheet.parentNode) {
document.head.appendChild(userSelectStyleSheet);
}
}
/**
* Enable user ability to select text on page
*/
export function enableUserSelect() {
if (userSelectStyleSheet.parentNode) {
userSelectStyleSheet.parentNode.removeChild(userSelectStyleSheet);
}
}
/**
* Get the metadata for a SVG container
*
* @param {SVGElement} svg The SVG container to get metadata for
*/
export function getMetadata(svg) {
return {
documentId: svg.getAttribute('data-pdf-annotate-document'),
pageNumber: parseInt(svg.getAttribute('data-pdf-annotate-page'), 10),
viewport: JSON.parse(svg.getAttribute('data-pdf-annotate-viewport'))
};
}