|
@@ -1,12 +1,21 @@
|
|
|
/* 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 } from '../constants/type';
|
|
|
+import { AnnotationType, Position, ViewportType } from '../constants/type';
|
|
|
import { xmlParser } from './dom';
|
|
|
-import { chunk } from './utility';
|
|
|
+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;
|
|
@@ -47,6 +56,7 @@ export const getAnnotationWithSelection = ({
|
|
|
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;
|
|
@@ -68,24 +78,34 @@ export const getAnnotationWithSelection = ({
|
|
|
textLayer.removeChild(startEle);
|
|
|
textLayer.removeChild(endEle);
|
|
|
|
|
|
- const info: AnnotationType = {};
|
|
|
+ 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;
|
|
|
+ let startY = startElement.offsetTop - EXTEND_RANGE;
|
|
|
let endX = endElement.offsetLeft + endElement.offsetWidth - endEleWidth;
|
|
|
- let endY = endElement.offsetTop + endElement.offsetHeight;
|
|
|
+ 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;
|
|
|
+ startY = endElement.offsetTop - EXTEND_RANGE;
|
|
|
endX = startElement.offsetLeft + startElement.offsetWidth - endEleWidth;
|
|
|
- endY = startElement.offsetTop + startElement.offsetHeight;
|
|
|
+ endY = startElement.offsetTop + startElement.offsetHeight + EXTEND_RANGE;
|
|
|
}
|
|
|
+ // @ts-ignore
|
|
|
+ const textElements = [...textLayer.childNodes];
|
|
|
|
|
|
- textLayer.childNodes.forEach((ele: any) => {
|
|
|
+ textElements.forEach((ele: any) => {
|
|
|
const {
|
|
|
offsetTop, offsetLeft, offsetHeight, offsetWidth,
|
|
|
} = ele;
|
|
@@ -104,40 +124,52 @@ export const getAnnotationWithSelection = ({
|
|
|
left: startX,
|
|
|
right: endX,
|
|
|
};
|
|
|
- } else if (
|
|
|
- (offsetTop > startY && offsetBottom < endY) || (offsetLeft >= startX && offsetRight <= endX)
|
|
|
- ) {
|
|
|
- // middle element
|
|
|
+ } else if (startElement === ele) {
|
|
|
+ // start element
|
|
|
coords = {
|
|
|
top: offsetTop,
|
|
|
bottom: offsetBottom,
|
|
|
- left: offsetLeft,
|
|
|
+ left: startX,
|
|
|
right: offsetRight,
|
|
|
};
|
|
|
- } else if (offsetTop === startY) {
|
|
|
- // start line element
|
|
|
+ } else if (endElement === ele) {
|
|
|
+ // end element
|
|
|
coords = {
|
|
|
top: offsetTop,
|
|
|
bottom: offsetBottom,
|
|
|
- left: offsetLeft <= startX ? startX : offsetLeft,
|
|
|
- right: offsetRight,
|
|
|
+ left: offsetLeft,
|
|
|
+ right: endX,
|
|
|
};
|
|
|
- } else if (offsetBottom === endY) {
|
|
|
- // end line element
|
|
|
+ } 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 >= endX ? endX : offsetRight,
|
|
|
+ right: offsetRight,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (coords.top && coords.left) {
|
|
|
+ coords = {
|
|
|
+ ...coords,
|
|
|
+ top: pageHeight - coords.top,
|
|
|
+ bottom: pageHeight - coords.bottom,
|
|
|
};
|
|
|
+
|
|
|
+ position.push(resetPositionValue(coords, scale));
|
|
|
}
|
|
|
- position.push(resetPositionValue(coords, scale));
|
|
|
}
|
|
|
});
|
|
|
|
|
|
info.obj_type = LINE_TYPE[type];
|
|
|
info.obj_attr = {
|
|
|
- page: startPageNum,
|
|
|
+ page: startPageNum - 1,
|
|
|
bdcolor: color,
|
|
|
position,
|
|
|
transparency: opacity * 0.01,
|
|
@@ -153,8 +185,11 @@ export const fetchXfdf = (token: string): Promise<any> => (
|
|
|
|
|
|
export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
|
|
|
const xmlDoc = xmlParser(xmlString);
|
|
|
- let annotations = xmlDoc.firstElementChild.children[0].children;
|
|
|
+ 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'
|
|
@@ -162,10 +197,10 @@ export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
|
|
|
|| cur.tagName === 'strikeout'
|
|
|
|| cur.tagName === 'squiggly'
|
|
|
) {
|
|
|
- let tempArray = [];
|
|
|
+ let tempArray: any[] = [];
|
|
|
if (cur.attributes.coords) {
|
|
|
const coords = cur.attributes.coords.value.split(',');
|
|
|
- tempArray = chunk(coords, 8);
|
|
|
+ tempArray = _.chunk(coords, 8);
|
|
|
}
|
|
|
|
|
|
const position = tempArray.map((ele: string[]) => ({
|
|
@@ -190,3 +225,84 @@ export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
|
|
|
|
|
|
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;
|
|
|
+};
|