|
@@ -1,179 +1,40 @@
|
|
|
-/* eslint-disable @typescript-eslint/camelcase */
|
|
|
import _ from 'lodash';
|
|
|
+import { v4 as uuidv4 } from 'uuid';
|
|
|
|
|
|
-import { LINE_TYPE } from '../constants';
|
|
|
-import { AnnotationType, PositionType, ViewportType } from '../constants/type';
|
|
|
-import { xmlParser } from './dom';
|
|
|
+import { ANNOTATION_TYPE } from '../constants';
|
|
|
+import {
|
|
|
+ AnnotationType, PositionType, ViewportType,
|
|
|
+} from '../constants/type';
|
|
|
import { getPdfPage, renderTextLayer } from './pdf';
|
|
|
+import { getPosition, parsePositionForBackend } from './position';
|
|
|
+import { normalizeRound, floatToHex } from './utility';
|
|
|
+import { xmlParser, getElementsByTagname } from './dom';
|
|
|
|
|
|
-const EXTEND_RANGE = 2;
|
|
|
-const FRACTIONDIGITS = 2;
|
|
|
+type GetFontAttributeFunc = (type: string, element: Record<string, any>) => Record<string, any>;
|
|
|
|
|
|
-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,
|
|
|
- }: PositionType,
|
|
|
- scale: number,
|
|
|
-): PositionType => ({
|
|
|
- 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: PositionType[] = [];
|
|
|
-
|
|
|
- // 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 + 5)
|
|
|
- || (offsetRight <= endX && offsetTop >= endX - offsetHeight - 5)
|
|
|
- ) {
|
|
|
- // middle element
|
|
|
- coords = {
|
|
|
- top: offsetTop,
|
|
|
- bottom: offsetBottom,
|
|
|
- left: offsetLeft,
|
|
|
- right: offsetRight,
|
|
|
- };
|
|
|
- }
|
|
|
+const getContent = (type: string, element: Record<string, any>): string => {
|
|
|
+ if (type !== 'Text' && type !== 'FreeText') return '';
|
|
|
|
|
|
- if (coords.top && coords.left) {
|
|
|
- coords = {
|
|
|
- ...coords,
|
|
|
- top: pageHeight - coords.top,
|
|
|
- bottom: pageHeight - coords.bottom,
|
|
|
- };
|
|
|
-
|
|
|
- position.push(resetPositionValue(coords, scale));
|
|
|
- }
|
|
|
+ let content = '';
|
|
|
+ const nodes: any = element.childNodes;
|
|
|
+ nodes.forEach((ele: HTMLElement) => {
|
|
|
+ if (ele.tagName === 'contents') {
|
|
|
+ content = ele.innerHTML;
|
|
|
}
|
|
|
});
|
|
|
+ return content;
|
|
|
+};
|
|
|
|
|
|
- info.obj_type = LINE_TYPE[type];
|
|
|
- info.obj_attr = {
|
|
|
- page: startPageNum - 1,
|
|
|
- bdcolor: color,
|
|
|
- position,
|
|
|
- transparency: opacity * 0.01,
|
|
|
- };
|
|
|
- appendInfo.push(info);
|
|
|
+const getFontAttribute: GetFontAttributeFunc = (type, element) => {
|
|
|
+ if (type !== 'FreeText') return {};
|
|
|
+ const appearanceString = element.childNodes[1].innerHTML;
|
|
|
+ const arr = appearanceString.split(' ');
|
|
|
|
|
|
- return appendInfo;
|
|
|
+ return {
|
|
|
+ fontsize: parseInt(arr[5], 10),
|
|
|
+ fontname: arr[4].substr(1),
|
|
|
+ textcolor: floatToHex(parseFloat(arr[0]), parseFloat(arr[1]), parseFloat(arr[2])),
|
|
|
+ };
|
|
|
};
|
|
|
|
|
|
export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
|
|
@@ -181,44 +42,36 @@ export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
|
|
|
|
|
|
const xmlDoc = xmlParser(xmlString);
|
|
|
const elements = xmlDoc.firstElementChild || xmlDoc.firstChild;
|
|
|
+ const element = getElementsByTagname(elements, 'annots') || [];
|
|
|
+ const annotations: any[] = Array.prototype.slice.call(element);
|
|
|
|
|
|
- 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),
|
|
|
- }));
|
|
|
+ const filterAnnots = annotations.reduce((acc: any[], cur: any) => {
|
|
|
+ const type = ANNOTATION_TYPE[cur.tagName];
|
|
|
|
|
|
+ if (type) {
|
|
|
+ const page = parseInt(cur.attributes.page.value, 10);
|
|
|
acc.push({
|
|
|
- obj_type: LINE_TYPE[cur.tagName],
|
|
|
+ id: uuidv4(),
|
|
|
+ obj_type: type,
|
|
|
obj_attr: {
|
|
|
- page: parseInt(cur.attributes.page.value, 10),
|
|
|
- bdcolor: cur.attributes.color.value,
|
|
|
- position,
|
|
|
- transparency: parseFloat(cur.attributes.opacity.value),
|
|
|
+ page,
|
|
|
+ position: getPosition(type, cur),
|
|
|
+ bdcolor: cur.attributes.color ? cur.attributes.color.value : undefined,
|
|
|
+ bdwidth: cur.attributes.width ? parseInt(cur.attributes.width.value, 10) : 0,
|
|
|
+ transparency: cur.attributes.opacity ? parseFloat(cur.attributes.opacity.value) : 1,
|
|
|
+ content: getContent(type, cur) || undefined,
|
|
|
+ fcolor: cur.attributes['interior-color'] ? cur.attributes['interior-color'].value : undefined,
|
|
|
+ ftransparency: cur.attributes['interior-opacity'] ? cur.attributes['interior-opacity'].value : undefined,
|
|
|
+ is_arrow: cur.attributes.tail,
|
|
|
+ ...getFontAttribute(type, cur),
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
+
|
|
|
return acc;
|
|
|
}, []);
|
|
|
|
|
|
- return filterAnnotations;
|
|
|
+ return filterAnnots;
|
|
|
};
|
|
|
|
|
|
// eslint-disable-next-line consistent-return
|
|
@@ -301,3 +154,41 @@ export const getAnnotationText = async ({
|
|
|
|
|
|
return text;
|
|
|
};
|
|
|
+
|
|
|
+export const parseAnnotationObject = ({
|
|
|
+ id,
|
|
|
+ obj_type,
|
|
|
+ obj_attr: {
|
|
|
+ page,
|
|
|
+ bdcolor,
|
|
|
+ transparency,
|
|
|
+ fcolor,
|
|
|
+ ftransparency,
|
|
|
+ position,
|
|
|
+ content,
|
|
|
+ style,
|
|
|
+ bdwidth,
|
|
|
+ fontname,
|
|
|
+ fontsize,
|
|
|
+ textcolor,
|
|
|
+ is_arrow,
|
|
|
+ },
|
|
|
+}: AnnotationType, pageHeight: number, scale: number): AnnotationType => ({
|
|
|
+ id,
|
|
|
+ obj_type,
|
|
|
+ obj_attr: {
|
|
|
+ page: page - 1,
|
|
|
+ bdcolor,
|
|
|
+ position: parsePositionForBackend(obj_type, position, pageHeight, scale),
|
|
|
+ transparency: transparency ? transparency * 0.01 : 0,
|
|
|
+ content: content || undefined,
|
|
|
+ style,
|
|
|
+ fcolor,
|
|
|
+ ftransparency: ftransparency ? ftransparency * 0.01 : 0,
|
|
|
+ bdwidth,
|
|
|
+ fontname,
|
|
|
+ fontsize,
|
|
|
+ textcolor,
|
|
|
+ is_arrow,
|
|
|
+ },
|
|
|
+});
|