UNPKG

@syncfusion/ej2-pdfviewer

Version:
375 lines (374 loc) 18.9 kB
/** * Redaction Overlay Text Module * * This module provides functionality to display overlay text on redaction annotations * when hovering over them, similar to how hover color changes work. * * @hidden */ var RedactionOverlayText = /** @class */ (function () { /** * Constructor for RedactionOverlayText * @param {any} pdfViewer - The PDF viewer instance * @param {any} pdfViewerBase - The viewer base instance */ function RedactionOverlayText(pdfViewer, pdfViewerBase) { this.pdfViewer = pdfViewer; this.pdfViewerBase = pdfViewerBase; } /** * Renders overlay text on a redaction annotation * @param {PdfAnnotationBaseModel} annotation - The redaction annotation * @param {any} currentAnnot - current annotation * @private * @returns {void} */ RedactionOverlayText.prototype.renderRedactionOverlayText = function (annotation, currentAnnot) { if (annotation && annotation.id !== 'diagram_helper' && annotation.overlayText !== '') { if (!annotation || !annotation.wrapper) { return; } // Use existing properties from the annotation var text = annotation.overlayText || 'REDACTED'; var textAlign = annotation.textAlign || 'center'; var isRepeat = annotation.isRepeat || false; var fontFamily = annotation.fontFamily || 'Helvetica'; var fontSize = annotation.fontSize || 12; var fontColor = annotation.fontColor || '#rgba(255, 0, 0, 1)'; // Red text // Remove any existing overlay text this.removeRedactionOverlayText(annotation); // Get the text layer for the page var textLayerId = this.pdfViewer.element.id + '_textLayer_' + annotation.pageIndex; var textLayer = document.getElementById(textLayerId); if (!textLayer) { return; } // Get the annotation bounds var bounds = annotation.bounds || { x: 0, y: 0, width: 0, height: 0 }; var zoomFactor = this.pdfViewer.viewerBase.getZoomFactor(); if (currentAnnot && currentAnnot.bounds.length) { var combineBounds = this.pdfViewer.annotation.redactionAnnotationModule.combineBounds(currentAnnot); for (var z = 0; z < combineBounds.length; z++) { // Create a container for the overlay text var overlayDiv = document.createElement('div'); overlayDiv.id = this.pdfViewer.element.id + '_redactionOverlay_' + (z + 1) + '_' + annotation.id; overlayDiv.className = 'e-pv-redaction-overlay-text'; // Set position and size overlayDiv.style.position = 'absolute'; overlayDiv.style.left = (combineBounds[z].x || 0) + 'px'; overlayDiv.style.top = ((combineBounds[z].y || 0)) + 'px'; overlayDiv.style.width = (combineBounds[z].width || 0) + 'px'; overlayDiv.style.height = ((combineBounds[z].height || 0)) + 'px'; // overlayDiv.style.padding = '1px'; overlayDiv.style.boxSizing = 'border-box'; overlayDiv.style.overflow = 'hidden'; overlayDiv.style.pointerEvents = 'none'; // Don't capture mouse events // Set text styles overlayDiv.style.fontFamily = fontFamily; overlayDiv.style.fontSize = (fontSize * zoomFactor) + 'px'; overlayDiv.style.color = fontColor; overlayDiv.style.textAlign = textAlign; // Add the overlay to the text layer textLayer.appendChild(overlayDiv); // Store this element reference for updating during resize/drag annotation.overlayElement = overlayDiv; if (isRepeat) { this.renderRepeatedTextHtml(overlayDiv, text, fontSize * zoomFactor, textAlign); } else { // For normal text, create a content div that handles word wrapping var contentDiv = document.createElement('div'); contentDiv.textContent = text; contentDiv.style.setProperty('overflow-wrap', 'break-word'); contentDiv.style.wordBreak = 'break-word'; contentDiv.style.width = '100%'; contentDiv.style.height = '100%'; contentDiv.style.display = 'flex'; contentDiv.style.flexDirection = 'column'; contentDiv.style.fontWeight = '550'; overlayDiv.appendChild(contentDiv); // Apply different wrapping behavior based on the height/width ratio if ((combineBounds[z].height || 0) > (combineBounds[z].width || 0) * 1.5) { // For narrow rectangles, use vertical text layout this.applyVerticalTextLayout(contentDiv, text, textAlign); } else { // For normal rectangles, use standard text layout contentDiv.style.justifyContent = 'flex-start'; contentDiv.style.alignItems = this.getAlignStyle(textAlign); contentDiv.textContent = text; } } } } else { // Create a container for the overlay text var overlayDiv = document.createElement('div'); overlayDiv.id = this.pdfViewer.element.id + '_redactionOverlay_' + annotation.id; overlayDiv.className = 'e-pv-redaction-overlay-text'; // Set position and size overlayDiv.style.position = 'absolute'; overlayDiv.style.left = ((bounds.x || 0) * zoomFactor) + 'px'; overlayDiv.style.top = ((bounds.y || 0) * zoomFactor) + 'px'; overlayDiv.style.width = ((bounds.width || 0) * zoomFactor) + 'px'; overlayDiv.style.height = ((bounds.height || 0) * zoomFactor) + 'px'; // overlayDiv.style.padding = '5px'; overlayDiv.style.boxSizing = 'border-box'; overlayDiv.style.overflow = 'hidden'; overlayDiv.style.pointerEvents = 'none'; // Don't capture mouse events // Set text styles overlayDiv.style.fontFamily = fontFamily; overlayDiv.style.fontSize = (fontSize * zoomFactor) + 'px'; overlayDiv.style.color = fontColor; overlayDiv.style.textAlign = textAlign; // Add the overlay to the text layer textLayer.appendChild(overlayDiv); // Store this element reference for updating during resize/drag annotation.overlayElement = overlayDiv; if (isRepeat) { this.renderRepeatedTextHtml(overlayDiv, text, fontSize * zoomFactor, textAlign); } else { // For normal text, create a content div that handles word wrapping var contentDiv = document.createElement('div'); contentDiv.textContent = text; contentDiv.style.setProperty('overflow-wrap', 'break-word'); contentDiv.style.wordBreak = 'break-word'; contentDiv.style.width = '100%'; contentDiv.style.height = '100%'; contentDiv.style.display = 'flex'; contentDiv.style.flexDirection = 'column'; contentDiv.style.fontWeight = '550'; overlayDiv.appendChild(contentDiv); // Apply different wrapping behavior based on the height/width ratio if ((bounds.height || 0) > (bounds.width || 0) * 1.5) { // For narrow rectangles, use vertical text layout this.applyVerticalTextLayout(contentDiv, text, textAlign); } else { // For normal rectangles, use standard text layout contentDiv.style.justifyContent = 'flex-start'; contentDiv.style.alignItems = this.getAlignStyle(textAlign); contentDiv.textContent = text; } } } } }; RedactionOverlayText.prototype.setAttributeHtml = function (element, attributes) { var keys = Object.keys(attributes); for (var i = 0; i < keys.length; i++) { if (keys[parseInt(i.toString(), 10)] !== 'style') { element.setAttribute(keys[parseInt(i.toString(), 10)], attributes[keys[parseInt(i.toString(), 10)]]); } else { this.applyStyleAgainstCsp(element, attributes[keys[parseInt(i.toString(), 10)]]); } } }; RedactionOverlayText.prototype.applyStyleAgainstCsp = function (svg, attributes) { var keys = attributes.split(';'); for (var i = 0; i < keys.length; i++) { var attribute = keys[parseInt(i.toString(), 10)].split(':'); if (attribute.length === 2) { svg.style[attribute[0].trim()] = attribute[1].trim(); } } }; /** * Removes overlay text from a redaction annotation * @param {any} annotation - The redaction annotation * @private * @returns {void} - void */ RedactionOverlayText.prototype.removeRedactionOverlayText = function (annotation) { if (!annotation) { return; } if (annotation.annotationAddMode === 'TextRedaction' || annotation.annotType === 'TextRedaction') { for (var z = 0; z < 5000; z++) { var overlayId = this.pdfViewer.element.id + '_redactionOverlay_' + (z + 1) + '_' + annotation.id; var overlayElement = document.getElementById(overlayId); if (overlayElement && overlayElement.parentNode) { overlayElement.parentNode.removeChild(overlayElement); } else { break; } } } else { var overlayId = this.pdfViewer.element.id + '_redactionOverlay_' + annotation.id; var overlayElement = document.getElementById(overlayId); if (overlayElement && overlayElement.parentNode) { overlayElement.parentNode.removeChild(overlayElement); } } }; /** * Gets the CSS align style based on text alignment * @param {string} textAlign - The text alignment (left, right, center) * @returns {string} The CSS align style */ RedactionOverlayText.prototype.getAlignStyle = function (textAlign) { switch (textAlign.toLowerCase()) { case 'left': return 'flex-start'; case 'right': return 'flex-end'; case 'center': return 'center'; default: return 'flex-start'; } }; /** * Applies vertical text layout for narrow redaction annotations * @param {HTMLElement} container - The container element * @param {string} text - The text to display * @param {string} textAlign - The text alignment * @returns {void} */ RedactionOverlayText.prototype.applyVerticalTextLayout = function (container, text, textAlign) { // Clear any existing content container.innerHTML = ''; // Set alignment container.style.alignItems = this.getAlignStyle(textAlign); container.style.fontWeight = '550'; // Break text into characters if needed for very narrow columns var containerWidth = container.parentElement ? parseFloat(container.parentElement.style.width) : 0; var words = text.split(' '); if (containerWidth < 30) { // For extremely narrow containers, stack letters vertically for (var i = 0; i < text.length; i++) { var charSpan = document.createElement('div'); charSpan.textContent = text[i]; charSpan.style.textAlign = textAlign; container.appendChild(charSpan); } } else { // For somewhat narrow containers, use word wrapping container.textContent = text; container.style.wordBreak = 'break-all'; } }; /** * Renders repeated text in a redaction annotation * @param {HTMLElement} container - The container element * @param {string} text - The text to repeat * @param {number} fontSize - The font size * @param {string} textAlign - The text alignment * @returns {void} - void */ RedactionOverlayText.prototype.renderRepeatedTextHtml = function (container, text, fontSize, textAlign) { // Clear existing content container.innerHTML = ''; // Create a wrapper for the repeated text var wrapperDiv = document.createElement('div'); wrapperDiv.style.display = 'flex'; wrapperDiv.style.flexDirection = 'column'; wrapperDiv.style.width = '100%'; wrapperDiv.style.height = '100%'; wrapperDiv.style.justifyContent = 'space-between'; // wrapperDiv.style.fontWeight = '550'; // Calculate approximate number of lines that can fit var lineHeight = fontSize; var containerHeight = parseFloat(container.style.height); var containerWidth = parseFloat(container.style.width); // Determine if annotation is narrow (vertical) or wide (horizontal) var isVertical = containerHeight > containerWidth * 1.5; // Calculate the number of lines and words per line var availableHeight = containerHeight - 10; // account for padding var lines = Math.max(1, Math.floor(availableHeight / lineHeight)); var availableWidth = containerWidth - 10; // account for padding // Create a temporary element to measure text width var measureElement = document.createElement('span'); measureElement.style.visibility = 'hidden'; measureElement.style.position = 'absolute'; measureElement.style.whiteSpace = 'nowrap'; measureElement.style.fontFamily = container.style.fontFamily; measureElement.style.fontSize = fontSize + 'px'; // measureElement.style.fontWeight = '550'; measureElement.textContent = text; // Include space after word document.body.appendChild(measureElement); // Get the actual width of the text var textWidth = measureElement.getBoundingClientRect().width; document.body.removeChild(measureElement); // Calculate how many repetitions fit in the available width var repetitionsPerLine = Math.floor(availableWidth / textWidth); if (repetitionsPerLine === 0) { repetitionsPerLine = 1; } // For vertical layouts, adjust text display if (isVertical) { // Create a full container with wrapped text var verticalDiv = document.createElement('div'); verticalDiv.style.width = '100%'; verticalDiv.style.height = '100%'; verticalDiv.style.display = 'flex'; verticalDiv.style.flexDirection = 'column'; verticalDiv.style.justifyContent = 'flex-start'; verticalDiv.style.textAlign = textAlign; // Add text content that will be wrapped var verticalText = ''; for (var i = 0; i < lines * 2; i++) { verticalText += text + ' '; } verticalDiv.textContent = verticalText; wrapperDiv.appendChild(verticalDiv); } else { // For each line, create a div for (var i = 0; i < lines; i++) { var lineDiv = document.createElement('div'); var requiredAttributes = { style: "width:100%; text-align:" + textAlign + "; white-space:nowrap; line-height:" + lineHeight + "px; overflow:hidden;" }; this.setAttributeHtml(lineDiv, requiredAttributes); // Calculate total width of repeated text var totalTextWidth = repetitionsPerLine * textWidth; // Calculate remaining space in the container //const remainingSpace: number = availableWidth - totalTextWidth; // Repeat text to fill the line based on alignment var lineText = ''; // Add appropriate spacing based on alignment if (textAlign.toLowerCase() === 'right') { // For right alignment, add space at the beginning //lineDiv.style.paddingLeft = remainingSpace + 'px'; } else if (textAlign.toLowerCase() === 'center') { // For center alignment, add half the space at the beginning //lineDiv.style.paddingLeft = (remainingSpace / 2) + 'px'; } // Add the repeated text for (var j = 0; j < repetitionsPerLine; j++) { lineText += text; } // Trim the last space if (lineText.length > 0) { lineText = lineText.substring(0, lineText.length - 1); } lineDiv.textContent = lineText; wrapperDiv.appendChild(lineDiv); } } container.appendChild(wrapperDiv); }; /** * Updates the redaction annotation after editing (resize/drag) * @param {PdfAnnotationBaseModel} annotation - The redaction annotation * @private * @returns {void} -void */ RedactionOverlayText.prototype.updateRedactionAfterEdit = function (annotation) { if (annotation && annotation.shapeAnnotationType === 'Redaction') { // Remove any existing overlay this.removeRedactionOverlayText(annotation); // Re-render the overlay with updated position this.renderRedactionOverlayText(annotation); // Update the rendering if (this.pdfViewer) { this.pdfViewer.renderDrawing(null, annotation.pageIndex); } } }; return RedactionOverlayText; }()); export { RedactionOverlayText };