annotation.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /* eslint-disable @typescript-eslint/camelcase */
  2. import fetch from 'isomorphic-unfetch';
  3. import config from '../config';
  4. import apiPath from '../constants/apiPath';
  5. import { LINE_TYPE } from '../constants';
  6. import { AnnotationType, Position } from '../constants/type';
  7. import { xmlParser } from './dom';
  8. import { chunk } from './utility';
  9. type Props = {
  10. color: string;
  11. type: string;
  12. opacity: number;
  13. scale: number;
  14. }
  15. const resetPositionValue = (
  16. {
  17. top, left, bottom, right,
  18. }: Position,
  19. scale: number,
  20. ): Position => ({
  21. top: top / scale,
  22. left: left / scale,
  23. bottom: bottom / scale,
  24. right: right / scale,
  25. });
  26. export const getAnnotationWithSelection = ({
  27. color, type, opacity, scale,
  28. }: Props): AnnotationType[] | null => {
  29. const selection: any = document.getSelection();
  30. if (!selection.rangeCount) return [];
  31. const {
  32. startContainer,
  33. startOffset,
  34. endContainer,
  35. endOffset,
  36. } = selection.getRangeAt(0);
  37. const appendInfo: AnnotationType[] = [];
  38. const startElement = startContainer.parentNode as HTMLElement;
  39. const endElement = endContainer.parentNode as HTMLElement;
  40. const startPage = startElement?.parentNode?.parentNode as HTMLElement;
  41. const endPage = endElement?.parentNode?.parentNode as HTMLElement;
  42. const startPageNum = parseInt(startPage.getAttribute('data-page-num') as string, 10);
  43. const endPageNum = parseInt(endPage.getAttribute('data-page-num') as string, 10);
  44. const textLayer = startPage.querySelector('[data-id="text-layer"]') as HTMLElement;
  45. if (startPageNum !== endPageNum) return null;
  46. if (startOffset === endOffset && startOffset === endOffset) return null;
  47. const startEle = startElement.cloneNode(true) as HTMLElement;
  48. const endEle = endElement.cloneNode(true) as HTMLElement;
  49. const startText = startElement.innerText.substring(0, startOffset);
  50. const endText = endEle.innerText.substring(endOffset);
  51. startEle.innerText = startText;
  52. endEle.innerText = endText;
  53. textLayer.appendChild(startEle);
  54. textLayer.appendChild(endEle);
  55. const startEleWidth = startEle.offsetWidth;
  56. const endEleWidth = endEle.offsetWidth;
  57. textLayer.removeChild(startEle);
  58. textLayer.removeChild(endEle);
  59. const info: AnnotationType = {};
  60. const position: Position[] = [];
  61. // left to right and up to down select
  62. let startX = startElement.offsetLeft + startEleWidth;
  63. let startY = startElement.offsetTop;
  64. let endX = endElement.offsetLeft + endElement.offsetWidth - endEleWidth;
  65. let endY = endElement.offsetTop + endElement.offsetHeight;
  66. if (startX > endX && startY >= endY) {
  67. // right to left and down to up select
  68. startX = endElement.offsetLeft + startEleWidth;
  69. startY = endElement.offsetTop;
  70. endX = startElement.offsetLeft + startElement.offsetWidth - endEleWidth;
  71. endY = startElement.offsetTop + startElement.offsetHeight;
  72. }
  73. textLayer.childNodes.forEach((ele: any) => {
  74. const {
  75. offsetTop, offsetLeft, offsetHeight, offsetWidth,
  76. } = ele;
  77. const offsetRight = offsetLeft + offsetWidth;
  78. const offsetBottom = offsetTop + offsetHeight;
  79. let coords = {
  80. top: 0, left: 0, right: 0, bottom: 0,
  81. };
  82. if (offsetTop >= startY && offsetBottom <= endY) {
  83. if (startElement === endElement) {
  84. // start and end same element
  85. coords = {
  86. top: offsetTop,
  87. bottom: offsetBottom,
  88. left: startX,
  89. right: endX,
  90. };
  91. } else if (
  92. (offsetTop > startY && offsetBottom < endY) || (offsetLeft >= startX && offsetRight <= endX)
  93. ) {
  94. // middle element
  95. coords = {
  96. top: offsetTop,
  97. bottom: offsetBottom,
  98. left: offsetLeft,
  99. right: offsetRight,
  100. };
  101. } else if (offsetTop === startY) {
  102. // start line element
  103. coords = {
  104. top: offsetTop,
  105. bottom: offsetBottom,
  106. left: offsetLeft <= startX ? startX : offsetLeft,
  107. right: offsetRight,
  108. };
  109. } else if (offsetBottom === endY) {
  110. // end line element
  111. coords = {
  112. top: offsetTop,
  113. bottom: offsetBottom,
  114. left: offsetLeft,
  115. right: offsetRight >= endX ? endX : offsetRight,
  116. };
  117. }
  118. position.push(resetPositionValue(coords, scale));
  119. }
  120. });
  121. info.obj_type = LINE_TYPE[type];
  122. info.obj_attr = {
  123. page: startPageNum,
  124. bdcolor: color,
  125. position,
  126. transparency: opacity * 0.01,
  127. };
  128. appendInfo.push(info);
  129. return appendInfo;
  130. };
  131. export const fetchXfdf = (token: string): Promise<any> => (
  132. fetch(`${config.API_HOST}${apiPath.getXfdf}?f=${token}`).then(res => res.text())
  133. );
  134. export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
  135. const xmlDoc = xmlParser(xmlString);
  136. let annotations = xmlDoc.firstElementChild.children[0].children;
  137. annotations = Array.prototype.slice.call(annotations);
  138. const filterAnnotations = annotations.reduce((acc: any[], cur: any) => {
  139. if (
  140. cur.tagName === 'highlight'
  141. || cur.tagName === 'underline'
  142. || cur.tagName === 'strikeout'
  143. || cur.tagName === 'squiggly'
  144. ) {
  145. let tempArray = [];
  146. if (cur.attributes.coords) {
  147. const coords = cur.attributes.coords.value.split(',');
  148. tempArray = chunk(coords, 8);
  149. }
  150. const position = tempArray.map((ele: string[]) => ({
  151. top: parseInt(ele[5] as string, 10),
  152. bottom: parseInt(ele[1], 10),
  153. left: parseInt(ele[0], 10),
  154. right: parseInt(ele[2], 10),
  155. }));
  156. acc.push({
  157. obj_type: LINE_TYPE[cur.tagName],
  158. obj_attr: {
  159. page: parseInt(cur.attributes.page.value, 10),
  160. bdcolor: cur.attributes.color.value,
  161. position,
  162. transparency: parseFloat(cur.attributes.opacity.value),
  163. },
  164. });
  165. }
  166. return acc;
  167. }, []);
  168. return filterAnnotations;
  169. };