markup.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { ANNOTATION_TYPE } from '../constants';
  2. import { parseAnnotationObject } from './annotation';
  3. type GetMarkupWithSelectionFunc = ({
  4. color,
  5. type,
  6. opacity,
  7. scale,
  8. }: {
  9. color: string;
  10. type: string;
  11. opacity: number;
  12. scale: number;
  13. }) => AnnotationType | null;
  14. export const getMarkupWithSelection: GetMarkupWithSelectionFunc = ({
  15. color,
  16. type,
  17. opacity,
  18. scale,
  19. }) => {
  20. const selection: Selection | null = window.getSelection();
  21. if (!selection || !selection.rangeCount) return null;
  22. const {
  23. startContainer,
  24. startOffset,
  25. endContainer,
  26. endOffset,
  27. } = selection.getRangeAt(0);
  28. const startElement = startContainer.parentNode as HTMLElement;
  29. const endElement = endContainer.parentNode as HTMLElement;
  30. const startPage = startElement?.parentNode?.parentNode as HTMLElement;
  31. const endPage = endElement?.parentNode?.parentNode as HTMLElement;
  32. const startPageNum = parseInt(
  33. startPage.getAttribute('data-page-num') as string,
  34. 10
  35. );
  36. const endPageNum = parseInt(
  37. endPage.getAttribute('data-page-num') as string,
  38. 10
  39. );
  40. const textLayer = startPage.querySelector(
  41. '[data-id="text-layer"]'
  42. ) as HTMLElement;
  43. const pageHeight = startPage.offsetHeight;
  44. if (startPageNum !== endPageNum) return null;
  45. if (startOffset === endOffset && startOffset === endOffset) return null;
  46. const startCloneEle = startElement.cloneNode(true) as HTMLElement;
  47. const endCloneEle = endElement.cloneNode(true) as HTMLElement;
  48. const startText = startElement.innerText.substring(0, startOffset);
  49. const endText = endElement.innerText.substring(endOffset);
  50. startCloneEle.innerText = startText;
  51. endCloneEle.innerText = endText;
  52. textLayer.appendChild(startCloneEle);
  53. textLayer.appendChild(endCloneEle);
  54. const startExtraWidth = startCloneEle.getBoundingClientRect().width;
  55. const endExtraWidth = endCloneEle.getBoundingClientRect().width;
  56. textLayer.removeChild(startCloneEle);
  57. textLayer.removeChild(endCloneEle);
  58. const info: AnnotationType = {
  59. obj_type: '',
  60. obj_attr: {
  61. page: 0,
  62. bdcolor: '',
  63. position: [],
  64. transparency: 0,
  65. },
  66. };
  67. const position: PositionType[] = [];
  68. const startRect = startElement.getBoundingClientRect();
  69. const endRect = endElement.getBoundingClientRect();
  70. // left to right and up to down select
  71. let startX = startElement.offsetLeft + startExtraWidth;
  72. let startY = startElement.offsetTop;
  73. let endX = endElement.offsetLeft + endRect.width - endExtraWidth;
  74. let endY = endElement.offsetTop + endRect.height;
  75. if (startX > endX && startY >= endY) {
  76. // right to left and down to up select
  77. startX = endElement.offsetLeft + startExtraWidth;
  78. startY = endElement.offsetTop;
  79. endX = startElement.offsetLeft + startRect.width - endExtraWidth;
  80. endY = startElement.offsetTop + startRect.height;
  81. }
  82. // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
  83. // @ts-ignore
  84. const textElements = [...textLayer.childNodes];
  85. textElements.forEach((ele: HTMLElement) => {
  86. const { offsetTop, offsetLeft, offsetHeight, offsetWidth } = ele;
  87. const offsetRight = offsetLeft + offsetWidth;
  88. const offsetBottom = offsetTop + offsetHeight;
  89. const coords = {
  90. top: offsetTop,
  91. bottom: offsetBottom,
  92. left: offsetLeft,
  93. right: offsetRight,
  94. };
  95. if (startElement === ele && endElement === ele) {
  96. // in same element
  97. coords.left = startX;
  98. coords.right = endX;
  99. position.push(coords);
  100. } else if (startElement === ele) {
  101. // in start element
  102. coords.left = startX;
  103. position.push(coords);
  104. } else if (endElement === ele) {
  105. // in end element
  106. coords.right = endX;
  107. position.push(coords);
  108. } else if (
  109. offsetTop >= startY + offsetHeight &&
  110. offsetBottom <= endY - offsetHeight
  111. ) {
  112. // in row
  113. position.push(coords);
  114. } else if (
  115. offsetLeft > startX &&
  116. offsetRight < endX &&
  117. offsetTop >= startY &&
  118. offsetBottom <= endY
  119. ) {
  120. // in line
  121. position.push(coords);
  122. } else if (offsetTop >= startY - 3 && offsetBottom <= endY + 3) {
  123. if (
  124. (offsetLeft > startX &&
  125. offsetTop >= startY - 3 &&
  126. offsetBottom < endY - offsetHeight) ||
  127. (offsetRight < endX &&
  128. offsetBottom <= endY + 3 &&
  129. offsetTop > startY + offsetHeight)
  130. ) {
  131. // in first line and end line
  132. position.push(coords);
  133. }
  134. }
  135. });
  136. for (let i = 1; i < position.length; i += 1) {
  137. if (
  138. position[i].top >= position[i - 1].top - 4 &&
  139. position[i].bottom <= position[i - 1].bottom + 4
  140. ) {
  141. position[i - 1].right = position[i].left;
  142. }
  143. }
  144. info.obj_type = ANNOTATION_TYPE[type];
  145. info.obj_attr = {
  146. page: startPageNum,
  147. bdcolor: color,
  148. position,
  149. transparency: opacity,
  150. };
  151. return parseAnnotationObject(info, pageHeight, scale);
  152. };
  153. export default getMarkupWithSelection;