/* eslint-disable no-param-reassign */ import { v4 as uuidv4 } from 'uuid'; import dayjs from 'dayjs'; import queryString from 'query-string'; import { ANNOTATION_TYPE, FORM_TYPE } from '../constants'; import { getPdfPage, renderTextLayer } from './pdf'; import { getPosition, parsePositionForBackend } from './position'; import { normalizeRound, floatToHex } from './utility'; import { xmlParser, getElementsByTagName } from './dom'; type GetFontAttributeFunc = ( type: string, element: HTMLElement, ) => Record; const getContent = (type: string, element: HTMLElement): string => { if (type !== 'Text' && type !== 'FreeText') return ''; let content = ''; const nodes = Array.prototype.slice.call(element.childNodes); nodes.forEach((ele: HTMLElement) => { if (ele.tagName === 'contents') { content = ele.innerHTML || ele.textContent || ''; } }); return content; }; const getFontAttribute: GetFontAttributeFunc = (type, element) => { if (type !== 'FreeText') return {}; const appearanceString = (element.childNodes[1] as HTMLElement).innerHTML || element.childNodes[1].textContent || ''; const arr = appearanceString.split(' '); 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[] => { if (!xmlString) return []; const xmlDoc = xmlParser(xmlString); const elements = xmlDoc.firstElementChild || xmlDoc.firstChild; const element = getElementsByTagName(elements as ChildNode, 'annots') || []; const annotations: HTMLElement[] = Array.prototype.slice.call(element); const filterAnnots = annotations.reduce( (acc: AnnotationType[], cur: HTMLElement) => { const type = ANNOTATION_TYPE[cur.tagName]; const attributes = cur.attributes as ElementAttributeType; if (type) { const page = parseInt(attributes.page.value, 10); acc.push({ id: uuidv4(), obj_type: type, obj_attr: { title: attributes.title ? attributes.title.value : undefined, date: attributes.date ? attributes.date.value : undefined, page, position: getPosition(type, cur), bdcolor: attributes.color ? attributes.color.value : undefined, bdwidth: attributes.width ? parseInt(attributes.width.value, 10) : 0, transparency: attributes.opacity ? parseFloat(attributes.opacity.value) : 1, content: getContent(type, cur) || undefined, fcolor: attributes['interior-color'] ? attributes['interior-color'].value : undefined, ftransparency: attributes['interior-opacity'] ? parseFloat(attributes['interior-opacity'].value) : undefined, is_arrow: !!attributes.tail, ...getFontAttribute(type, cur), }, }); } return acc; }, [], ); return filterAnnots; }; export const parseFormElementFromXml = ( xmlString: string, ): AnnotationType[] => { if (!xmlString) return []; const xmlDoc = xmlParser(xmlString); const elements = xmlDoc.firstElementChild || xmlDoc.firstChild; const element = getElementsByTagName(elements as ChildNode, 'widgets') || []; const annotations: HTMLElement[] = Array.prototype.slice.call(element); const filterForm = annotations.reduce( (acc: AnnotationType[], cur: HTMLElement) => { const type = FORM_TYPE[cur.tagName]; const attributes = cur.attributes as ElementAttributeType; if (type) { const page = parseInt(attributes.page.value, 10); acc.push({ id: uuidv4(), obj_type: type, obj_attr: { date: attributes.date ? attributes.date.value : undefined, page, position: getPosition(type, cur), bdcolor: attributes.color ? attributes.color.value : undefined, style: attributes.style ? attributes.style.value : undefined, bdwidth: attributes.width ? parseInt(attributes.width.value, 10) : 0, transparency: attributes.opacity ? parseFloat(attributes.opacity.value) : 1, }, }); } return acc; }, [], ); return filterForm; }; // eslint-disable-next-line consistent-return const getEleText = ( coord: PositionType, elements: HTMLElement[], viewport: ViewportType, scale: number, ): 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: PositionType[]; pdf: PdfType; }): Promise => { 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, }); } const textElements = Array.prototype.slice.call(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; }; export const parseAnnotationObject = ( { id, obj_type, obj_attr: { page, bdcolor, transparency, fcolor, ftransparency, position = { left: 0, top: 0, right: 0, bottom: 0 }, content, style, bdwidth, fontname, fontsize, textcolor, is_arrow, src, }, }: AnnotationType, pageHeight: number, scale: number, ): AnnotationType => ({ id: id || uuidv4(), 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, src, }, }); export const appendUserIdAndDate = ( annotateObj: AnnotationType, ): AnnotationType => { const parsed = queryString.parse(window.location.search); if (parsed.watermark) { annotateObj.obj_attr.title = parsed.watermark; } const datetime = dayjs().format('YYYY-MM-DD_HH:mm:ss'); annotateObj.obj_attr.date = datetime; return annotateObj; };