123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- /* eslint-disable @typescript-eslint/camelcase */
- import fetch from 'isomorphic-unfetch';
- import _ from 'lodash';
- import config from '../config';
- import apiPath from '../constants/apiPath';
- import { LINE_TYPE } from '../constants';
- import { AnnotationType, Position, ViewportType } from '../constants/type';
- import { xmlParser } from './dom';
- import { getPdfPage, renderTextLayer } from './pdf';
- const EXTEND_RANGE = 2;
- const FRACTIONDIGITS = 2;
- const normalizeRound = (num: number, fractionDigits?: number): number => {
- const frac = fractionDigits || FRACTIONDIGITS;
- return Math.round(num * (10 ** frac)) / (10 ** frac);
- };
- type Props = {
- color: string;
- type: string;
- opacity: number;
- scale: number;
- }
- const resetPositionValue = (
- {
- top, left, bottom, right,
- }: Position,
- scale: number,
- ): Position => ({
- top: top / scale,
- left: left / scale,
- bottom: bottom / scale,
- right: right / scale,
- });
- export const getAnnotationWithSelection = ({
- color, type, opacity, scale,
- }: Props): AnnotationType[] | null => {
- const selection: any = document.getSelection();
- if (!selection.rangeCount) return [];
- const {
- startContainer,
- startOffset,
- endContainer,
- endOffset,
- } = selection.getRangeAt(0);
- const appendInfo: AnnotationType[] = [];
- const startElement = startContainer.parentNode as HTMLElement;
- const endElement = endContainer.parentNode as HTMLElement;
- const startPage = startElement?.parentNode?.parentNode as HTMLElement;
- const endPage = endElement?.parentNode?.parentNode as HTMLElement;
- const startPageNum = parseInt(startPage.getAttribute('data-page-num') as string, 10);
- const endPageNum = parseInt(endPage.getAttribute('data-page-num') as string, 10);
- const textLayer = startPage.querySelector('[data-id="text-layer"]') as HTMLElement;
- const pageHeight = startPage.offsetHeight;
- if (startPageNum !== endPageNum) return null;
- if (startOffset === endOffset && startOffset === endOffset) return null;
- const startEle = startElement.cloneNode(true) as HTMLElement;
- const endEle = endElement.cloneNode(true) as HTMLElement;
- const startText = startElement.innerText.substring(0, startOffset);
- const endText = endEle.innerText.substring(endOffset);
- startEle.innerText = startText;
- endEle.innerText = endText;
- textLayer.appendChild(startEle);
- textLayer.appendChild(endEle);
- const startEleWidth = startEle.offsetWidth;
- const endEleWidth = endEle.offsetWidth;
- textLayer.removeChild(startEle);
- textLayer.removeChild(endEle);
- const info: AnnotationType = {
- obj_type: '',
- obj_attr: {
- page: 0,
- bdcolor: '',
- position: [],
- transparency: 0,
- },
- };
- const position: Position[] = [];
- // left to right and up to down select
- let startX = startElement.offsetLeft + startEleWidth;
- let startY = startElement.offsetTop - EXTEND_RANGE;
- let endX = endElement.offsetLeft + endElement.offsetWidth - endEleWidth;
- let endY = endElement.offsetTop + endElement.offsetHeight + EXTEND_RANGE;
- if (startX > endX && startY >= endY) {
- // right to left and down to up select
- startX = endElement.offsetLeft + startEleWidth;
- startY = endElement.offsetTop - EXTEND_RANGE;
- endX = startElement.offsetLeft + startElement.offsetWidth - endEleWidth;
- endY = startElement.offsetTop + startElement.offsetHeight + EXTEND_RANGE;
- }
- // @ts-ignore
- const textElements = [...textLayer.childNodes];
- textElements.forEach((ele: any) => {
- const {
- offsetTop, offsetLeft, offsetHeight, offsetWidth,
- } = ele;
- const offsetRight = offsetLeft + offsetWidth;
- const offsetBottom = offsetTop + offsetHeight;
- let coords = {
- top: 0, left: 0, right: 0, bottom: 0,
- };
- if (offsetTop >= startY && offsetBottom <= endY) {
- if (startElement === endElement) {
- // start and end same element
- coords = {
- top: offsetTop,
- bottom: offsetBottom,
- left: startX,
- right: endX,
- };
- } else if (startElement === ele) {
- // start element
- coords = {
- top: offsetTop,
- bottom: offsetBottom,
- left: startX,
- right: offsetRight,
- };
- } else if (endElement === ele) {
- // end element
- coords = {
- top: offsetTop,
- bottom: offsetBottom,
- left: offsetLeft,
- right: endX,
- };
- } else if (
- (offsetLeft >= startX && offsetRight <= endX)
- || (offsetTop > (startY + 5) && offsetBottom < (endY - 5))
- || (offsetLeft >= startX && offsetBottom <= startY + offsetHeight)
- || (offsetRight <= endX && offsetTop >= endX - offsetHeight)
- ) {
- // middle element
- coords = {
- top: offsetTop,
- bottom: offsetBottom,
- left: offsetLeft,
- right: offsetRight,
- };
- }
- if (coords.top && coords.left) {
- coords = {
- ...coords,
- top: pageHeight - coords.top,
- bottom: pageHeight - coords.bottom,
- };
- position.push(resetPositionValue(coords, scale));
- }
- }
- });
- info.obj_type = LINE_TYPE[type];
- info.obj_attr = {
- page: startPageNum - 1,
- bdcolor: color,
- position,
- transparency: opacity * 0.01,
- };
- appendInfo.push(info);
- return appendInfo;
- };
- export const fetchXfdf = (token: string): Promise<any> => (
- fetch(`${config.API_HOST}${apiPath.getXfdf}?f=${token}`).then(res => res.text())
- );
- export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
- const xmlDoc = xmlParser(xmlString);
- const elements = xmlDoc.firstElementChild || xmlDoc.firstChild;
- let annotations = elements.childNodes[1].childNodes;
- annotations = Array.prototype.slice.call(annotations);
- const filterAnnotations = annotations.reduce((acc: any[], cur: any) => {
- if (
- cur.tagName === 'highlight'
- || cur.tagName === 'underline'
- || cur.tagName === 'strikeout'
- || cur.tagName === 'squiggly'
- ) {
- let tempArray: any[] = [];
- if (cur.attributes.coords) {
- const coords = cur.attributes.coords.value.split(',');
- tempArray = _.chunk(coords, 8);
- }
- const position = tempArray.map((ele: string[]) => ({
- top: parseInt(ele[5] as string, 10),
- bottom: parseInt(ele[1], 10),
- left: parseInt(ele[0], 10),
- right: parseInt(ele[2], 10),
- }));
- acc.push({
- obj_type: LINE_TYPE[cur.tagName],
- obj_attr: {
- page: parseInt(cur.attributes.page.value, 10),
- bdcolor: cur.attributes.color.value,
- position,
- transparency: parseFloat(cur.attributes.opacity.value),
- },
- });
- }
- return acc;
- }, []);
- return filterAnnotations;
- };
- // eslint-disable-next-line consistent-return
- const getEleText = (coord: any, elements: any, viewport: any, scale: any): string => {
- const top = normalizeRound(viewport.height - coord.top * scale);
- const left = normalizeRound(coord.left * scale);
- const bottom = normalizeRound(viewport.height - coord.bottom * scale);
- const right = normalizeRound(coord.right * scale);
- for (let i = 0, len = elements.length; i <= len; i += 1) {
- const element = elements[i];
- if (element) {
- const eleTop = normalizeRound(element.offsetTop);
- const eleLeft = normalizeRound(element.offsetLeft);
- const eleRight = normalizeRound(element.offsetLeft + element.offsetWidth);
- if (eleTop >= top && eleTop <= bottom) {
- const textLength = element.innerText.length;
- const width = element.offsetWidth;
- if (eleLeft < left && eleRight > right) {
- const distanceL = left - eleLeft;
- const rateL = distanceL / width;
- const start = Math.floor(textLength * rateL);
- const distanceR = eleRight - right;
- const rateR = distanceR / width;
- const end = Math.floor(textLength - (textLength * rateR));
- return ` ${element.innerText.slice(start, end)}`;
- }
- if (eleLeft < left && eleRight > left) {
- const distance = left - eleLeft;
- const rate = distance / width;
- const start = Math.floor(textLength * rate);
- return ` ${element.innerText.slice(start)}`;
- }
- if (eleRight > right && eleLeft < right) {
- const distance = eleRight - right;
- const rate = distance / width;
- const end = Math.floor(textLength - (textLength * rate));
- return ` ${element.innerText.slice(0, end)}`;
- }
- if (eleLeft >= left && eleRight <= right) {
- return ` ${element.innerText}`;
- }
- }
- }
- }
- return '';
- };
- export const getAnnotationText = async ({
- viewport, scale, page, coords, pdf,
- }: {
- viewport: ViewportType;
- scale: number;
- page: number;
- coords: Position[];
- pdf: any;
- }): Promise<any> => {
- const pageContainer = document.getElementById(`page_${page}`) as HTMLElement;
- const textLayer = pageContainer.querySelector('[data-id="text-layer"]') as HTMLElement;
- const pdfPage = await getPdfPage(pdf, page);
- if (!textLayer.childNodes.length) {
- await renderTextLayer({
- textLayer,
- pdfPage,
- viewport,
- });
- }
- // @ts-ignore
- const textElements = [...textLayer.childNodes];
- let text = '';
- for (let i = 0, len = coords.length; i < len; i += 1) {
- const coord = coords[i];
- text += getEleText(coord, textElements, viewport, scale);
- }
- return text;
- };
|