layer.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. var LAYER_CHANGE_ACTIVE_CLASS = 'layerChange-active'
  2. // 获取 button 元素
  3. var layerChangeButton = document.getElementById('layerChange');
  4. var isLayerChangeButtonActive = () => {
  5. return layerChangeButton.classList.contains(LAYER_CHANGE_ACTIVE_CLASS)
  6. }
  7. var getLocale = () => {
  8. console.log('test-iframe-locale', localStorage.getItem('umi-locale') ,localStorage.getItem('locale') , '')
  9. return localStorage.getItem('umi_locale') || localStorage.getItem('locale') || 'zh-CN';
  10. }
  11. // 添加tooltip
  12. function createHoverTooltip(element, tooltipText, id, options = {}) {
  13. const tooltip = document.createElement('div');
  14. tooltip.className = `tooltip-${id} tooltip`;
  15. tooltip.style.display = 'none';
  16. tooltip.style.zIndex = '999';
  17. document.body.appendChild(tooltip);
  18. const defaultOptions = {
  19. offset: { y: 10 },
  20. delay: 200,
  21. duration: 200,
  22. };
  23. const mergedOptions = { ...defaultOptions, ...options };
  24. let tooltipTimer;
  25. function positionTooltip() {
  26. const rect = element.getBoundingClientRect();
  27. const tooltipRect = tooltip.getBoundingClientRect();
  28. const left = rect.left + (rect.width - tooltipRect.width) / 2;
  29. const top = rect.bottom + mergedOptions.offset.y;
  30. tooltip.style.left = `${left}px`;
  31. tooltip.style.top = `${top}px`;
  32. }
  33. function showTooltip() {
  34. tooltip.textContent = tooltipText;
  35. tooltip.style.display = 'block';
  36. tooltip.style.opacity = '0';
  37. tooltip.style.transition = `opacity ${mergedOptions.duration}ms`;
  38. positionTooltip();
  39. setTimeout(() => {
  40. tooltip.style.opacity = '1';
  41. }, 10);
  42. }
  43. function hideTooltip() {
  44. tooltip.style.opacity = '0';
  45. setTimeout(() => {
  46. tooltip.style.display = 'none';
  47. }, mergedOptions.duration);
  48. }
  49. element.addEventListener('mouseenter', () => {
  50. tooltipTimer = setTimeout(showTooltip, mergedOptions.delay);
  51. });
  52. element.addEventListener('mouseleave', () => {
  53. clearTimeout(tooltipTimer);
  54. hideTooltip();
  55. });
  56. window.addEventListener('resize', positionTooltip);
  57. window.addEventListener('scroll', positionTooltip);
  58. // 返回对象,包含 updateTooltipText 函数
  59. return {
  60. updateTooltipText: (newText) => {
  61. tooltipText = newText;
  62. positionTooltip()
  63. if (tooltip.style.display !== 'none') {
  64. showTooltip();
  65. tooltipText = newText;
  66. }
  67. }
  68. };
  69. }
  70. const tooltipLayer = createHoverTooltip(layerChangeButton, getLocale() === 'zh-CN' ? '隐藏识别结果': 'Hide recognition results', 'layerChange', { offset: { y: 22 } });
  71. window.addEventListener('storage', function(event) {
  72. // 检查事件是否与监听的键相关
  73. if (event.key === 'umi_locale'|| event.key === 'locale') {
  74. const text = isLayerChangeButtonActive() ? event?.newValue === 'zh-CN' ? '隐藏识别结果': 'Hide recognition results': getLocale() === 'zh-CN' ? '显示识别结果': 'Display recognition results'
  75. tooltipLayer?.updateTooltipText(text)
  76. }
  77. });
  78. // 添加点击事件监听器
  79. layerChangeButton.addEventListener('click', function() {
  80. // 检查当前 button 的选中状态
  81. if (isLayerChangeButtonActive()) {
  82. // 如果已经处于选中状态,则移除选中状态的 class
  83. const annotationLayerList = document.getElementsByClassName('annotationLayer')
  84. Array?.from(annotationLayerList)?.forEach(element => {
  85. var extractLayer = element.querySelector('#extractLayer');
  86. if(extractLayer) {
  87. extractLayer.style.opacity = 0
  88. }
  89. });
  90. console.log('test-dd', annotationLayerList, typeof annotationLayerList)
  91. tooltipLayer?.updateTooltipText( getLocale() === 'zh-CN' ? '显示识别结果': 'Display recognition results')
  92. this.classList.remove(LAYER_CHANGE_ACTIVE_CLASS);
  93. } else {
  94. // 如果未处于选中状态,则添加选中状态的 class
  95. this.classList.add(LAYER_CHANGE_ACTIVE_CLASS);
  96. tooltipLayer?.updateTooltipText(getLocale() === 'zh-CN' ? '隐藏识别结果': 'Hide recognition results')
  97. const scale = 0.943 / 0.7071 * window?.PDFViewerApplication?.pdfViewer?._currentScale || 1
  98. window.renderExtractLayer(window.pdfExtractData, Number(0), scale)
  99. const annotationLayerList = document.getElementsByClassName('annotationLayer')
  100. Array?.from(annotationLayerList)?.forEach(element => {
  101. var extractLayer = element.querySelector('#extractLayer');
  102. if(extractLayer) {
  103. extractLayer.style.opacity = 1
  104. }
  105. })
  106. }
  107. });
  108. // 获取显示消息的元素
  109. const messageDisplay = document.getElementById('messageDisplay');
  110. function removeDuplicates(arr) {
  111. return [...new Set(arr)];
  112. }
  113. window.addEventListener('error', function(event) {
  114. if (event.target && event.target.tagName === 'SCRIPT') {
  115. console.error("Script error detected: ", event);
  116. }
  117. }, true);
  118. // 添加消息监听器
  119. window.addEventListener('message', function(event) {
  120. const receivedMessage = event.data;
  121. const data = receivedMessage?.data
  122. const type = receivedMessage?.type
  123. function setHasRenderAnimatedPage (num) {
  124. if(!window.hasRenderAnimatedPage ) {
  125. window.hasRenderAnimatedPage = []
  126. }
  127. if( typeof window.hasRenderAnimatedPage === 'object'){
  128. window.hasRenderAnimatedPage = removeDuplicates([...window.hasRenderAnimatedPage, num])
  129. }
  130. }
  131. let animatingBox = new Map()
  132. function renderExtractLayer(data, pageNum, scale) {
  133. // 判断按钮是开的还是关的
  134. if(!isLayerChangeButtonActive()) return;
  135. const bboxes = data?.[pageNum]?.bboxes || []
  136. function drawBoxes(boxes, scale) {
  137. if(animatingBox.get(pageNum)) return
  138. // const annotationLayer = document.querySelector('.canvasWrapper');
  139. const pageLayer = document.getElementsByClassName('page')?.[pageNum]
  140. const annotationLayer = pageLayer.querySelectorAll('.annotationLayer')?.[0];
  141. // annotationLayer.removeAttribute('hidden');
  142. if(!annotationLayer) {
  143. // console.error('error: annotationLayer has not been rendered')
  144. return
  145. }
  146. const extractLayer = annotationLayer.querySelector('#extractLayer');
  147. // 因为pdfjs只会缓存8页的内容,所以采用每次切换移除重建canvas的方式
  148. if (extractLayer) {
  149. extractLayer?.remove();
  150. }
  151. annotationLayer.style.width = '100%';
  152. annotationLayer.style.height = '100%'
  153. annotationLayer.style.position = 'absolute';
  154. annotationLayer.style.top = 0;
  155. annotationLayer.style.left = 0;
  156. const computedLayer = document.querySelector('.canvasWrapper');
  157. const canvas = document.createElement('canvas');
  158. canvas.id = 'extractLayer'
  159. const w = pageLayer?.offsetWidth - 18;
  160. const h = pageLayer?.offsetHeight - 18;
  161. canvas.width = true ? `${w}` : '100%';
  162. canvas.height = true ? `${h}`: '100%';
  163. canvas.style.width = true ? `${w}px` : '100%';
  164. canvas.style.height = true ? `${h}px`: '100%';
  165. canvas.style.position = 'absolute';
  166. canvas.style.top = 0;
  167. canvas.style.left = 0;
  168. annotationLayer.append(canvas)
  169. const ctx = canvas.getContext('2d');
  170. // 移除之前的画布内容
  171. // ctx.clearRect(0, 0, canvas.width, canvas.height);
  172. ctx.save();
  173. // console.log('renderExtractLayer: draw boxes')
  174. function drawPartialRect(ctx, box, progress, scale) {
  175. const [x, y, x2, y2]= box.bbox.map((i) => (i * scale));
  176. const width = x2 - x;
  177. const height = y2 - y;
  178. const color = box?.color?.line
  179. const fillColor = box?.color?.fill
  180. ctx.beginPath();
  181. ctx.strokeStyle = color;
  182. // 左边竖线
  183. ctx.moveTo(x, y);
  184. ctx.lineTo(x, y2);
  185. // 上边横线
  186. ctx.moveTo(x, y);
  187. ctx.lineTo(x + width * (progress < 0 ? 0: progress), y);
  188. // 右边竖线 (只在进度完成时绘制)
  189. if (progress === 1) {
  190. ctx.moveTo(x2, y);
  191. ctx.lineTo(x2, y2);
  192. ctx.fillStyle = fillColor;
  193. ctx.fillRect(x, y, width, height);
  194. }
  195. // 下边横线
  196. ctx.moveTo(x, y2);
  197. ctx.lineTo(x + width * (progress < 0 ? 0: progress), y2);
  198. ctx.stroke();
  199. }
  200. function fillRect(ctx, box, scale) {
  201. const [x, y, x2, y2]= box.bbox.map((i) => (i * scale));
  202. const width = x2 - x;
  203. const height = y2 - y;
  204. const color = box?.color?.fill
  205. ctx.fillStyle = color;
  206. ctx.fillRect(x, y, width, height);
  207. }
  208. function animateBox(ctx, box, duration = 1000) {
  209. const startTime = performance.now();
  210. function animate(currentTime) {
  211. const elapsedTime = currentTime - startTime;
  212. const progress = Math.min(elapsedTime / duration, 1);
  213. // ctx.clearRect(...box.bbox); // 清除之前的绘制
  214. drawPartialRect(ctx, box, progress, scale);
  215. if (progress < 1) {
  216. requestAnimationFrame(animate);
  217. }
  218. }
  219. requestAnimationFrame(animate);
  220. }
  221. async function animateAllBoxes() {
  222. // const [index, value] of array.entries()
  223. for (const [index, box] of boxes?.entries()) {
  224. await animateBox(ctx, box, 600); // 动画时间改为500ms
  225. await new Promise(resolve => setTimeout(resolve, 200)); // 每个框之间的延迟也减少到100ms
  226. }
  227. // 所有线框动画完成后,一次性填充所有矩形
  228. // ctx.clearRect(...box.bbox); // 清除之前的绘制
  229. // boxes.forEach(box => fillRect(ctx, box, scale));
  230. console.log("test-animate All animations completed and boxes filled");
  231. animatingBox.set(pageNum, false)
  232. }
  233. boxes.forEach((box, index) => {
  234. drawPartialRect(ctx, box, 1, scale);
  235. });
  236. canvas.style.width = false ? `${w}px` : '100%';
  237. canvas.style.height = false ? `${h}px`: '100%';
  238. ctx.restore();
  239. }
  240. !!bboxes?.length&&drawBoxes(bboxes, scale);
  241. }
  242. // init extractLayer data
  243. if(type === 'initExtractLayerData') {
  244. const scale = 0.943 / 0.7071 * window?.PDFViewerApplication?.pdfViewer?._currentScale || 1
  245. const currentPageNumber = window?.PDFViewerApplication?.pdfViewer?._currentPageNumber || 1
  246. window.pdfExtractData = data;
  247. window.renderExtractLayer = renderExtractLayer
  248. // window.renderExtractLayer(window.pdfExtractData, currentPageNumber - 1, scale)
  249. // use the picture view rather than outlined view
  250. window.renderExtractLayer(window.pdfExtractData, Number(0), scale)
  251. window?.PDFViewerApplication?.pdfSidebar?.switchView(1, false)
  252. }
  253. if(type === 'pageChange') {
  254. if(window.renderExtractLayer
  255. && window.pdfExtractData
  256. ) {
  257. const scale = 0.943 / 0.7071 * window?.PDFViewerApplication?.pdfViewer?._currentScale || 1
  258. const currentPageNumber = data || 0
  259. window.renderExtractLayer(window.pdfExtractData, Number(0), scale)
  260. window.renderExtractLayer(window.pdfExtractData, Number(data), scale)
  261. } else if(!window.pdfExtractData) {
  262. // console.error('extract pdf render data has not been initialized')
  263. }
  264. }
  265. if( type === 'title') {
  266. const odlPdfTitle = document.getElementById("odl-pdf-title");
  267. odlPdfTitle.innerText = data;
  268. }
  269. if( type === 'setPage') {
  270. window?.PDFViewerApplication?.eventBus?.dispatch("pagenumberchanged", {
  271. value: data
  272. })
  273. }
  274. if( type === '') {
  275. }
  276. });