import { PDFViewerApplication } from "../pdfjs-legacy/js/viewer.mjs";
import UUID from "./uuid.js";
let zoomScaleValue = 1.0;
let activeOverlay;
let drawingLayer = null;
const doNothing = () => {};
function addRedactedPagePreview(pagesSelector) {
document.querySelectorAll(pagesSelector).forEach((page) => {
let textLayer = page.querySelector(".textLayer");
if (textLayer) textLayer.classList.add("redacted-page-preview");
});
}
function addRedactedThumbnailPreview(sidebarPagesSelector) {
document.querySelectorAll(sidebarPagesSelector).forEach((thumbnail) => {
thumbnail.classList.add("redacted-thumbnail-preview");
let thumbnailImage = thumbnail.querySelector(".thumbnailImage");
if (thumbnailImage)
thumbnailImage.classList.add("redacted-thumbnail-image-preview");
});
}
function removeRedactedPagePreview() {
document
.querySelectorAll(".textLayer")
.forEach((textLayer) =>
textLayer.classList.remove("redacted-page-preview")
);
document
.querySelectorAll("#thumbnailView > a > div.thumbnail")
.forEach((thumbnail) => {
thumbnail.classList.remove("redacted-thumbnail-preview");
let thumbnailImage = thumbnail.querySelector(".thumbnailImage");
if (thumbnailImage)
thumbnailImage.classList.remove("redacted-thumbnail-image-preview");
});
}
function extractPagesDetailed(pagesInput, totalPageCount) {
let parts = pagesInput.split(",").filter((s) => s);
let pagesDetailed = {
numbers: new Set(),
functions: new Set(),
ranges: new Set(),
all: false,
};
for (let part of parts) {
let trimmedPart = part.trim();
if ("all" == trimmedPart) {
pagesDetailed.all = true;
return pagesDetailed;
} else if (isValidFunction(trimmedPart)) {
pagesDetailed.functions.add(formatNFunction(trimmedPart));
} else if (trimmedPart.includes("-")) {
let range = trimmedPart
.replaceAll(" ", "")
.split("-")
.filter((s) => s);
if (
range &&
range.length == 2 &&
range[0].trim() > 0 &&
range[1].trim() > 0
)
pagesDetailed.ranges.add({
low: range[0].trim(),
high: range[1].trim(),
});
} else if (isPageNumber(trimmedPart)) {
pagesDetailed.numbers.add(
trimmedPart <= totalPageCount ? trimmedPart : totalPageCount
);
}
}
return pagesDetailed;
}
function formatNFunction(expression) {
let result = insertMultiplicationBeforeN(expression.replaceAll(" ", ""));
let multiplyByOpeningRoundBracketPattern = /([0-9n)])\(/g; // example: n(n-1), 9(n-1), (n-1)(n-2)
result = result.replaceAll(multiplyByOpeningRoundBracketPattern, "$1*(");
let multiplyByClosingRoundBracketPattern = /\)([0-9n)])/g; // example: (n-1)n, (n-1)9, (n-1)(n-2)
result = result.replaceAll(multiplyByClosingRoundBracketPattern, ")*$1");
return result;
}
function insertMultiplicationBeforeN(expression) {
let result = expression.replaceAll(/(\d)n/g, "$1*n");
while (result.match(/nn/)) {
result = result.replaceAll(/nn/g, "n*n"); // From nn -> n*n
}
return result;
}
function validatePages(pages) {
let parts = pages.split(",").filter((s) => s);
let errors = [];
for (let part of parts) {
let trimmedPart = part.trim();
if ("all" == trimmedPart) continue;
else if (trimmedPart.includes("n")) {
if (!isValidFunction(trimmedPart))
errors.push(
`${trimmedPart} is an invalid function, it should consist of digits 0-9, n, *, -, /, (, ), \\.`
);
} else if (trimmedPart.includes("-")) {
let range = trimmedPart.split("-").filter((s) => s);
if (!range || range.length != 2)
errors.push(
`${trimmedPart} is an invalid range, it should consist of from-to, example: 1-5`
);
else if (range[0].trim() <= 0 || range[1].trim() <= 0)
errors.push(
`${trimmedPart} has invalid range(s), page numbers should be positive.`
);
} else if (!isPageNumber(trimmedPart)) {
errors.push(
`${trimmedPart} is invalid, it should either be a function, page number or a range.`
);
}
}
return { errors };
}
function isPageNumber(page) {
return /^[0-9]*$/.test(page);
}
function isValidFunction(part) {
return part.includes("n") && /[0-9n+\-*/() ]+$/.test(part);
}
function hideContainer(container) {
container?.classList.add("d-none");
}
const RedactionModes = Object.freeze({
DRAWING: Symbol("drawing"),
TEXT: Symbol("text"),
NONE: Symbol("none"),
});
function removePDFJSButtons() {
document.getElementById("print")?.remove();
document.getElementById("download")?.remove();
document.getElementById("editorStamp")?.remove();
document.getElementById("editorFreeText")?.remove();
document.getElementById("editorInk")?.remove();
document.getElementById("secondaryToolbarToggle")?.remove();
document.getElementById("openFile")?.remove();
}
function hideInitialPage() {
document.body.style.overflowY = "hidden";
let redactionsFormContainer = document.getElementById(
"redactionFormContainer"
);
for (
let el = redactionsFormContainer.previousElementSibling;
el && el instanceof HTMLBRElement;
el = el.previousElementSibling
) {
el.classList.add("d-none");
}
redactionsFormContainer.classList.add("d-none");
document.getElementsByTagName("footer")[0].classList.add("d-none");
}
window.addEventListener("load", (e) => {
let isChromium =
!!window.chrome ||
(!!navigator.userAgentData &&
navigator.userAgentData.brands.some((data) => data.brand == "Chromium"));
let isSafari =
/constructor/i.test(window.HTMLElement) ||
(function (p) {
return p.toString() === "[object SafariRemoteNotification]";
})(
!window["safari"] ||
(typeof safari !== "undefined" && window["safari"].pushNotification)
);
let isWebkit = navigator.userAgent.search(/webkit/i) > 0;
let isGecko = navigator.userAgent.search(/gecko/i) > 0;
let isFirefox = typeof InstallTrigger !== "undefined";
let hiddenInput = document.getElementById("fileInput");
let outerContainer = document.getElementById("outerContainer");
let printContainer = document.getElementById("printContainer");
let toolbarViewerRight = document.getElementById("toolbarViewerRight");
let showMoreBtn = document.getElementById("showMoreBtn");
window.onresize = (e) => {
if (window.innerWidth > 1125 && showMoreBtn.classList.contains("toggled")) {
showMoreBtn.click();
} else if (
window.innerWidth > 1125 &&
toolbarViewerRight.hasAttribute("style")
) {
toolbarViewerRight.style.removeProperty("display");
}
};
showMoreBtn.onclick = (e) => {
if (showMoreBtn.classList.contains("toggled")) {
toolbarViewerRight.style.display = "none";
showMoreBtn.classList.remove("toggled");
} else {
toolbarViewerRight.style.display = "flex";
showMoreBtn.classList.add("toggled");
}
};
let viewer = document.getElementById("viewer");
hiddenInput.files = undefined;
let redactionMode = RedactionModes.NONE;
let redactions = [];
let redactionsInput = document.getElementById("redactions-input");
let redactionsPalette = document.getElementById("redactions-palette");
let redactionsPaletteInput = redactionsPalette.querySelector("input");
let redactionsPaletteContainer = document.getElementById(
"redactionsPaletteContainer"
);
let applyRedactionBtn = document.getElementById("apply-redaction");
let redactedPagesDetails = {
numbers: new Set(),
ranges: new Set(),
functions: new Set(),
all: false,
};
let pageBasedRedactionBtn = document.getElementById("pageBasedRedactionBtn");
let pageBasedRedactionOverlay = document.getElementById(
"pageBasedRedactionOverlay"
);
pageBasedRedactionBtn.onclick = (e) =>
pageBasedRedactionOverlay.classList.remove("d-none");
pageBasedRedactionOverlay.querySelector("input[type=text]").onchange = (
e
) => {
let input = e.target;
let parentElement = input.parentElement;
resetFieldFeedbackMessages(input, parentElement);
let value = input.value.trim();
let { errors } = validatePages(value);
if (errors && errors.length > 0) {
applyPageRedactionBtn.disabled = "true";
displayFieldErrorMessages(input, errors);
} else {
applyPageRedactionBtn.removeAttribute("disabled");
input.classList.add("is-valid");
}
};
let applyPageRedactionBtn = document.getElementById("applyPageRedactionBtn");
applyPageRedactionBtn.onclick = (e) => {
pageBasedRedactionOverlay.querySelectorAll("input").forEach((input) => {
const id = input.getAttribute("data-for");
if (id == "pageNumbers") {
let { errors } = validatePages(input.value);
resetFieldFeedbackMessages(input, input.parentElement);
if (errors?.length > 0) {
applyPageRedactionBtn.disabled = true;
displayFieldErrorMessages(input, errors);
} else {
pageBasedRedactionOverlay.classList.add("d-none");
applyRedactionBtn.removeAttribute("disabled");
input.classList.remove("is-valid");
let totalPagesCount = PDFViewerApplication.pdfViewer.pagesCount;
let pagesDetailed = extractPagesDetailed(
input.value,
totalPagesCount
);
redactedPagesDetails = pagesDetailed;
addPageRedactionPreviewToPages(pagesDetailed, totalPagesCount);
}
} else if (id == "pageRedactColor") setPageRedactionColor(input.value);
let formInput = document.getElementById(id);
if (formInput) formInput.value = input.value;
});
};
let closePageRedactionBtn = document.getElementById("closePageRedactionBtn");
closePageRedactionBtn.onclick = (e) => {
pageBasedRedactionOverlay.classList.add("d-none");
pageBasedRedactionOverlay.querySelectorAll("input").forEach((input) => {
const id = input.getAttribute("data-for");
if (id == "pageNumbers") {
resetFieldFeedbackMessages(input, input.parentElement);
}
let formInput = document.getElementById(id);
if (formInput) input.value = formInput.value;
});
};
let pdfToImageCheckbox = document.getElementById("convertPDFToImage");
let pdfToImageBtn = document.getElementById("pdfToImageBtn");
pdfToImageBtn.onclick = (e) => {
pdfToImageBtn.classList.toggle("btn-success");
pdfToImageBtn.classList.toggle("btn-danger");
pdfToImageCheckbox.checked = !pdfToImageCheckbox.checked;
};
let fileChooser = document.getElementsByClassName("custom-file-chooser")[0];
let fileChooserInput = fileChooser.querySelector(
`#${fileChooser.getAttribute("data-bs-element-id")}`
);
let uploadButton = document.getElementById("uploadBtn");
uploadButton.onclick = (e) => fileChooserInput.click();
document.addEventListener("file-input-change", (e) => {
redactions = [];
_setRedactionsInput(redactions);
});
let submitBtn = document.getElementById("submitBtn");
let downloadBtn = document.getElementById("downloadBtn");
let downloadBtnIcon = document.getElementById("downloadBtnIcon");
downloadBtn.onclick = (e) => {
submitBtn.click();
setTimeout(_showOrHideLoadingSpinner, 100); // wait 100 milliseconds so that submitBtn would be disabled
};
function _showOrHideLoadingSpinner() {
if (!submitBtn.disabled) {
downloadBtnIcon.innerHTML = "download";
downloadBtnIcon.classList.remove("spin-animation");
return;
}
downloadBtnIcon.innerHTML = "progress_activity";
downloadBtnIcon.classList.add("spin-animation");
setTimeout(_showOrHideLoadingSpinner, 500);
}
redactionsPaletteContainer.onclick = (e) => redactionsPalette.click();
viewer.onmouseup = (e) => {
if (redactionMode !== RedactionModes.TEXT) return;
const containsText =
window.getSelection() && window.getSelection().toString() != "";
applyRedactionBtn.disabled = !containsText;
};
applyRedactionBtn.onclick = (e) => {
if (redactionMode !== RedactionModes.TEXT) {
applyRedactionBtn.disabled = true;
return;
}
redactTextSelection();
applyRedactionBtn.disabled = true;
};
redactionsPaletteInput.onchange = (e) => {
let color = e.target.value;
redactionsPalette.style.setProperty("--palette-color", color);
};
document.addEventListener("file-input-change", (e) => {
let fileChooser = document.getElementsByClassName("custom-file-chooser")[0];
let fileChooserInput = fileChooser.querySelector(
`#${fileChooser.getAttribute("data-bs-element-id")}`
);
hiddenInput.files = fileChooserInput.files;
if (!hiddenInput.files || hiddenInput.files.length === 0) {
hideContainer(outerContainer);
hideContainer(printContainer);
} else {
outerContainer?.classList.remove("d-none");
printContainer?.classList.remove("d-none");
hideInitialPage();
}
hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
});
PDFViewerApplication.downloadOrSave = doNothing;
PDFViewerApplication.triggerPrinting = doNothing;
let redactionContainersDivs = {};
PDFViewerApplication.eventBus.on("pagerendered", (e) => {
removePDFJSButtons();
let textSelectionRedactionBtn = document.getElementById(
"man-text-select-redact"
);
let drawRedactionBtn = document.getElementById("man-shape-redact");
textSelectionRedactionBtn.onclick = _handleTextSelectionRedactionBtnClick;
drawRedactionBtn.onclick = _handleDrawRedactionBtnClick;
let layer = e.source.textLayer.div;
layer.setAttribute("data-page", e.pageNumber);
if (
redactedPagesDetails.all ||
redactedPagesDetails.numbers.has(e.pageNumber)
) {
layer.classList.add("redacted-page-preview");
} else {
layer.classList.remove("redacted-page-preview");
}
zoomScaleValue = e.source.scale ? e.source.scale : e.source.pageScale;
document.documentElement.style.setProperty("--zoom-scale", zoomScaleValue);
let redactionsContainer = document.getElementById(
`redactions-container-${e.pageNumber}`
);
if (!redactionsContainer && !redactionContainersDivs[`${e.pageNumber}`]) {
redactionsContainer = document.createElement("div");
redactionsContainer.style.position = "relative";
redactionsContainer.style.height = "100%";
redactionsContainer.style.width = "100%";
redactionsContainer.id = `redactions-container-${e.pageNumber}`;
redactionsContainer.style.setProperty("z-index", "unset");
layer.appendChild(redactionsContainer);
redactionContainersDivs[`${e.pageNumber}`] = redactionsContainer;
} else if (
!redactionsContainer &&
redactionContainersDivs[`${e.pageNumber}`]
) {
redactionsContainer = redactionContainersDivs[`${e.pageNumber}`];
layer.appendChild(redactionsContainer);
// Dispatch event to update text layer references for elements' events
redactionsContainer
.querySelectorAll(".selected-wrapper")
.forEach((area) =>
area.dispatchEvent(
new CustomEvent("textLayer-reference-changed", {
bubbles: true,
detail: { textLayer: layer },
})
)
);
}
document.onpointerup = (e) => {
if (drawingLayer && e.target != drawingLayer && e.button == 0)
drawingLayer.dispatchEvent(new Event("external-pointerup"));
};
initDraw(layer, redactionsContainer);
function _handleTextSelectionRedactionBtnClick(e) {
if (textSelectionRedactionBtn.classList.contains("toggled")) {
resetTextSelection();
} else {
resetDrawRedactions();
textSelectionRedactionBtn.classList.add("toggled");
redactionMode = RedactionModes.TEXT;
const containsText =
window.getSelection() && window.getSelection().toString() != "";
applyRedactionBtn.disabled = !containsText;
applyRedactionBtn.classList.remove("d-none");
}
}
function resetTextSelection() {
textSelectionRedactionBtn.classList.remove("toggled");
redactionMode = RedactionModes.NONE;
clearSelection();
applyRedactionBtn.disabled = true;
applyRedactionBtn.classList.add("d-none");
}
function clearSelection() {
if (window.getSelection) {
if (window.getSelection().empty) {
// Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) {
// Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// IE?
document.selection.empty();
}
}
function _handleDrawRedactionBtnClick(e) {
if (drawRedactionBtn.classList.contains("toggled")) {
resetDrawRedactions();
} else {
resetTextSelection();
drawRedactionBtn.classList.add("toggled");
document.documentElement.style.setProperty(
"--textLayer-pointer-events",
"none"
);
document.documentElement.style.setProperty(
"--textLayer-user-select",
"none"
);
redactionMode = RedactionModes.DRAWING;
}
}
function resetDrawRedactions() {
redactionMode = RedactionModes.NONE;
drawRedactionBtn.classList.remove("toggled");
document.documentElement.style.setProperty(
"--textLayer-pointer-events",
"auto"
);
document.documentElement.style.setProperty(
"--textLayer-user-select",
"auto"
);
window.dispatchEvent(new CustomEvent("reset-drawing", { bubbles: true }));
}
function initDraw(canvas, redactionsContainer) {
let mouse = {
x: 0,
y: 0,
startX: 0,
startY: 0,
};
let element = null;
let drawnRedaction = null;
window.addEventListener("reset-drawing", (e) => {
_clearDrawing();
canvas.style.cursor = "default";
document.documentElement.style.setProperty(
"--textLayer-pointer-events",
"auto"
);
document.documentElement.style.setProperty(
"--textLayer-user-select",
"auto"
);
});
window.addEventListener("drawing-entered", (e) => {
let target = e.detail?.target;
if (canvas === target) return;
_clearDrawing();
});
window.addEventListener("cancel-drawing", (e) => {
_clearDrawing();
canvas.style.cursor = "default";
});
function setMousePosition(e) {
if (isChromium || isSafari || isWebkit) {
mouse.x = e.offsetX;
mouse.y = e.offsetY;
} else if (isFirefox || isGecko) {
mouse.x = e.layerX;
mouse.y = e.layerY;
} else {
let rect = (e.target || e.srcElement).getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
}
}
window.onkeydown = (e) => {
if (e.key === "Escape" && redactionMode === RedactionModes.DRAWING) {
window.dispatchEvent(
new CustomEvent("cancel-drawing", { bubbles: true })
);
}
};
canvas.onpointerenter = (e) => {
window.dispatchEvent(
new CustomEvent("drawing-entered", {
bubbles: true,
detail: { target: canvas },
})
);
};
canvas.onpointerup = (e) => {
let isLeftClick = e.button == 0;
if (!isLeftClick) return;
if (element !== null) {
_saveAndResetDrawnRedaction();
console.log("finished.");
}
};
canvas.addEventListener("external-pointerup", (e) => {
if (element != null) {
_saveAndResetDrawnRedaction();
}
});
canvas.onpointerleave = (e) => {
let ev = copyEvent(e, "pointerleave");
let { left, top } = calculateMouseCoordinateToRotatedBox(canvas, e);
ev.layerX = left;
ev.offsetX = left;
ev.layerY = top;
ev.offsetY = top;
setMousePosition(ev);
if (element !== null) {
draw();
}
};
canvas.onpointerdown = (e) => {
let isLeftClick = e.button == 0;
if (!isLeftClick) return;
if (element == null) {
if (redactionMode !== RedactionModes.DRAWING) {
console.warn(
"Drawing attempt when redaction mode is",
redactionMode.description
);
return;
}
console.log("begun.");
_captureAndDrawStartingPointOfDrawnRedaction();
}
};
canvas.onpointermove = function (e) {
setMousePosition(e);
if (element !== null) {
draw();
}
};
function draw() {
let scaleFactor = _getScaleFactor();
let width = Math.abs(mouse.x - mouse.startX);
element.style.width = _toCalcZoomPx(_scaleToDisplay(width));
let height = Math.abs(mouse.y - mouse.startY);
element.style.height = _toCalcZoomPx(_scaleToDisplay(height));
let left = mouse.x - mouse.startX < 0 ? mouse.x : mouse.startX;
element.style.left = _toCalcZoomPx(_scaleToDisplay(left));
let top = mouse.y - mouse.startY < 0 ? mouse.y : mouse.startY;
element.style.top = _toCalcZoomPx(_scaleToDisplay(top));
if (drawnRedaction) {
drawnRedaction.left = _scaleToPDF(left, scaleFactor);
drawnRedaction.top = _scaleToPDF(top, scaleFactor);
drawnRedaction.width = _scaleToPDF(width, scaleFactor);
drawnRedaction.height = _scaleToPDF(height, scaleFactor);
}
}
function _clearDrawing() {
if (element) element.remove();
if (drawingLayer == canvas) drawingLayer = null;
element = null;
drawnRedaction = null;
}
function _saveAndResetDrawnRedaction() {
if (!element) return;
if (
!element.style.height ||
element.style.height.includes("(0px * var") ||
!element.style.width ||
element.style.width.includes("(0px * var")
) {
element.remove();
} else {
element.classList.add("selected-wrapper");
element.classList.remove("rectangle");
addRedactionOverlay(element, drawnRedaction, canvas);
redactions.push(drawnRedaction);
_setRedactionsInput(redactions);
}
drawingLayer = null;
element = null;
drawnRedaction = null;
canvas.style.cursor = "default";
}
function _captureAndDrawStartingPointOfDrawnRedaction() {
mouse.startX = mouse.x;
mouse.startY = mouse.y;
element = document.createElement("div");
element.className = "rectangle";
drawingLayer = canvas;
let left = mouse.x;
let top = mouse.y;
element.style.left = _toCalcZoomPx(_scaleToDisplay(left));
element.style.top = _toCalcZoomPx(_scaleToDisplay(top));
let scaleFactor = _getScaleFactor();
let color = redactionsPalette.style.getPropertyValue("--palette-color");
element.style.setProperty("--palette-color", color);
drawnRedaction = {
left: _scaleToPDF(left, scaleFactor),
top: _scaleToPDF(top, scaleFactor),
width: 0.0,
height: 0.0,
color: color,
pageNumber: parseInt(canvas.getAttribute("data-page")),
element: element,
id: UUID.uuidv4(),
};
redactionsContainer.appendChild(element);
canvas.style.cursor = "crosshair";
}
}
});
PDFViewerApplication.eventBus.on("rotationchanging", (e) => {
if (!activeOverlay) return;
hideOverlay();
});
function _getScaleFactor() {
return parseFloat(viewer.style.getPropertyValue("--scale-factor"));
}
function getTextLayer(element) {
let current = element;
while (current) {
if (
current instanceof HTMLDivElement &&
current.classList.contains("textLayer")
)
return current;
current = current.parentElement;
}
return current;
}
document.onclick = (e) => {
if (
(e.target &&
e.target.classList.contains("selected-wrapper") &&
e.target.firstChild == activeOverlay) ||
e.target == activeOverlay
)
return;
if (activeOverlay) hideOverlay();
};
document.addEventListener("keydown", (e) => {
if (e.key === "Delete" && activeOverlay) {
activeOverlay
.querySelector(".delete-icon")
?.dispatchEvent(new Event("click", { bubbles: true }));
return;
}
const isRedactionShortcut =
e.ctrlKey && (e.key == "s" || e.key == "S" || e.code == "KeyS");
if (!isRedactionShortcut || redactionMode !== RedactionModes.TEXT) return;
redactTextSelection();
});
function rotateTextBox(rect, textLayerRect, angle) {
let left, top, width, height;
if (!angle || angle == 0) {
left = rect.left - textLayerRect.left;
top = rect.top - textLayerRect.top;
width = rect.width;
height = rect.height;
} else if (angle == 90) {
left = rect.top - textLayerRect.top;
top = textLayerRect.right - rect.right;
width = rect.height;
height = rect.width;
} else if (angle == 180) {
left = textLayerRect.right - rect.right;
top = textLayerRect.bottom - rect.bottom;
width = rect.width;
height = rect.height;
} else if (angle == 270) {
left = textLayerRect.bottom - rect.bottom;
top = rect.left - textLayerRect.left;
width = rect.height;
height = rect.width;
}
return { left, top, width, height };
}
function redactTextSelection() {
let selection = window.getSelection();
if (!selection || selection.rangeCount <= 0) return;
let range = selection.getRangeAt(0);
let textLayer = getTextLayer(range.startContainer);
if (!textLayer) return;
const pageNumber = textLayer.getAttribute("data-page");
let redactionsArea = textLayer.querySelector(
`#redactions-container-${pageNumber}`
);
let textLayerRect = textLayer.getBoundingClientRect();
let rects = range.getClientRects();
let scaleFactor = _getScaleFactor();
let color = redactionsPalette.style.getPropertyValue("--palette-color");
let angle = textLayer.getAttribute("data-main-rotation");
for (const rect of rects) {
if (!rect || !rect.width || !rect.height) continue;
let redactionElement = document.createElement("div");
redactionElement.classList.add("selected-wrapper");
let { left, top, width, height } = rotateTextBox(
rect,
textLayerRect,
angle
);
let leftDisplayScaled = _scaleToDisplay(left);
let topDisplayScaled = _scaleToDisplay(top);
let widthDisplayScaled = _scaleToDisplay(width);
let heightDisplayScaled = _scaleToDisplay(height);
let redactionInfo = {
left: _scaleToPDF(left, scaleFactor),
top: _scaleToPDF(top, scaleFactor),
width: _scaleToPDF(width, scaleFactor),
height: _scaleToPDF(height, scaleFactor),
pageNumber: parseInt(pageNumber),
color: color,
element: redactionElement,
id: UUID.uuidv4(),
};
redactions.push(redactionInfo);
redactionElement.style.left = _toCalcZoomPx(leftDisplayScaled);
redactionElement.style.top = _toCalcZoomPx(topDisplayScaled);
redactionElement.style.width = _toCalcZoomPx(widthDisplayScaled);
redactionElement.style.height = _toCalcZoomPx(heightDisplayScaled);
redactionElement.style.setProperty("--palette-color", color);
redactionsArea.appendChild(redactionElement);
addRedactionOverlay(redactionElement, redactionInfo, textLayer);
}
_setRedactionsInput(redactions);
applyRedactionBtn.disabled = true;
}
function _scaleToDisplay(value) {
return value / zoomScaleValue;
}
function _scaleToPDF(value, scaleFactor) {
if (!scaleFactor)
scaleFactor = document.documentElement.getPropertyValue("--scale-factor");
return value / scaleFactor;
}
function _toCalcZoomPx(val) {
return `calc(${val}px * var(--zoom-scale))`;
}
function _setRedactionsInput(redactions) {
let stringifiedRedactions = JSON.stringify(
redactions.filter(_nonEmptyRedaction).map((red) => ({
x: red.left,
y: red.top,
width: red.width,
height: red.height,
color: red.color,
page: red.pageNumber,
}))
);
redactionsInput.value = stringifiedRedactions;
}
function addRedactionOverlay(redactionElement, redactionInfo, textLayer) {
let redactionOverlay = document.createElement("div");
let deleteBtn = $(
``
)[0];
deleteBtn.onclick = (e) => {
redactions = redactions.filter((red) => redactionInfo.id != red.id);
redactionElement.remove();
_setRedactionsInput(redactions);
activeOverlay = null;
};
let colorPaletteLabel = $(
``
)[0];
let colorPaletteInput = $(`
`)[0];
colorPaletteLabel.appendChild(colorPaletteInput);
colorPaletteLabel.onclick = (e) => {
if (colorPaletteLabel === e.target) {
e.stopPropagation();
}
};
colorPaletteInput.onchange = (e) => {
let color = e.target.value;
redactionElement.style.setProperty("--palette-color", color);
let redactionIdx = redactions.findIndex(
(red) => redactionInfo.id === red.id
);
if (redactionIdx < 0) return;
redactions[redactionIdx].color = color;
_setRedactionsInput(redactions);
};
redactionOverlay.appendChild(deleteBtn);
redactionOverlay.appendChild(colorPaletteLabel);
redactionOverlay.classList.add("redaction-overlay");
redactionOverlay.style.display = "none";
redactionElement.addEventListener("textLayer-reference-changed", (e) => {
textLayer = e.detail.textLayer;
});
redactionElement.onclick = (e) => {
if (e.target != redactionElement) return;
if (activeOverlay) hideOverlay();
redactionElement.classList.add("active-redaction");
activeOverlay = redactionOverlay;
_adjustActiveOverlayCoordinates();
};
redactionElement.appendChild(redactionOverlay);
// Adjust active overlay coordinates to avoid placing the overlay out of page bounds
function _adjustActiveOverlayCoordinates() {
activeOverlay.style.visibility = "hidden";
activeOverlay.style.display = "flex";
textLayer = textLayer || getTextLayer(redactionElement);
let angle = parseInt(textLayer.getAttribute("data-main-rotation"));
if (textLayer)
redactionOverlay.style.transform = `rotate(${angle * -1}deg)`;
activeOverlay.style.removeProperty("left");
activeOverlay.style.removeProperty("top");
let textRect = textLayer.getBoundingClientRect();
let overlayRect = redactionOverlay.getBoundingClientRect();
let leftOffset = 0,
topOffset = 0;
if (overlayRect.right > textRect.right) {
leftOffset = textRect.right - overlayRect.right;
} else if (overlayRect.left < textRect.left) {
leftOffset = textRect.left - overlayRect.left;
}
if (overlayRect.top < textRect.top) {
topOffset = textRect.top - overlayRect.top;
} else if (overlayRect.bottom > textRect.bottom) {
topOffset = textRect.bottom - overlayRect.bottom;
}
switch (angle) {
case 90:
[leftOffset, topOffset] = [topOffset, -leftOffset];
break;
case 180:
[leftOffset, topOffset] = [-leftOffset, -topOffset];
break;
case 270:
[leftOffset, topOffset] = [-topOffset, leftOffset];
break;
}
if (leftOffset != 0)
activeOverlay.style.left = `calc(50% + ${leftOffset}px`;
if (topOffset != 0)
activeOverlay.style.top = `calc(100% + ${topOffset}px`;
activeOverlay.style.visibility = "unset";
}
}
});
function calculateMouseCoordinateToRotatedBox(canvas, e) {
let textRect = canvas.getBoundingClientRect();
let left,
top = 0;
let angle = parseInt(canvas.getAttribute("data-main-rotation"));
switch (angle) {
case 0:
left = clamp(e.pageX - textRect.left, 0, textRect.width);
top = clamp(e.pageY - textRect.top, 0, textRect.height);
break;
case 90:
left = clamp(e.pageY - textRect.top, 0, textRect.height);
top = clamp(textRect.right - e.pageX, 0, textRect.width);
break;
case 180:
left = clamp(textRect.right - e.pageX, 0, textRect.width);
top = clamp(textRect.bottom - e.pageY, 0, textRect.width);
break;
case 270:
left = clamp(textRect.bottom - e.pageY, 0, textRect.height);
top = clamp(e.pageX - textRect.left, 0, textRect.width);
break;
}
return { left, top };
}
function clamp(value, min, max) {
return Math.max(min, Math.min(value, max));
}
function addPageRedactionPreviewToPages(pagesDetailed, totalPagesCount) {
if (pagesDetailed.all) {
addRedactedPagePreview("#viewer > .page");
addRedactedThumbnailPreview("#thumbnailView > a > div.thumbnail");
} else {
removeRedactedPagePreview();
setPageNumbersFromRange(pagesDetailed, totalPagesCount);
setPageNumbersFromNFunctions(pagesDetailed, totalPagesCount);
let pageNumbers = Array.from(pagesDetailed.numbers);
if (pageNumbers?.length > 0) {
let pagesSelector = pageNumbers
.map((number) => `#viewer > .page[data-page-number="${number}"]`)
.join(",");
addRedactedPagePreview(pagesSelector);
let thumbnailSelector = pageNumbers
.map(
(number) =>
`#thumbnailView > a > div.thumbnail[data-page-number="${number}"]`
)
.join(",");
addRedactedThumbnailPreview(thumbnailSelector);
}
}
}
function resetFieldFeedbackMessages(input, parentElement) {
if (parentElement)
parentElement
.querySelectorAll(".invalid-feedback")
.forEach((feedback) => feedback.remove());
if (input) {
input.classList.remove("is-invalid");
input.classList.remove("is-valid");
}
}
function displayFieldErrorMessages(input, errors) {
input.classList.add("is-invalid");
errors.forEach((error) => {
let element = document.createElement("div");
element.classList.add("invalid-feedback");
element.classList.add("list-styling");
element.textContent = error;
input.parentElement.appendChild(element);
});
}
function setPageRedactionColor(color) {
document.documentElement.style.setProperty("--page-redaction-color", color);
}
function setPageNumbersFromNFunctions(pagesDetailed, totalPagesCount) {
pagesDetailed.functions.forEach((fun) => {
if (!isValidFunction(fun)) return;
for (let n = 1; n <= totalPagesCount; n++) {
let pageNumber = eval(fun);
if (!pageNumber || pageNumber <= 0 || pageNumber > totalPagesCount)
continue;
pagesDetailed.numbers.add(pageNumber);
}
});
}
function setPageNumbersFromRange(pagesDetailed, totalPagesCount) {
pagesDetailed.ranges.forEach((range) => {
for (let i = range.low; i <= range.high && i <= totalPagesCount; i++) {
pagesDetailed.numbers.add(i);
}
});
}
function hideOverlay() {
activeOverlay.style.display = "none";
activeOverlay.parentElement.classList.remove("active-redaction");
activeOverlay = null;
}
function _isEmptyRedaction(redaction) {
return (
redaction.left == null ||
redaction.top == null ||
redaction.width == null ||
redaction.height == null ||
redaction.pageNumber == null
);
}
function _nonEmptyRedaction(redaction) {
return !_isEmptyRedaction(redaction);
}
function copyEvent(e, type) {
if (type == "pointerleave")
return {
layerX: e.layerX,
layerY: e.layerY,
pageX: e.pageX,
pageY: e.pageY,
clientX: e.clientX,
clientY: e.clientY,
button: e.button,
height: e.height,
width: e.width,
offsetX: e.offsetX,
offsetY: e.offsetY,
pointerId: e.pointerId,
pointerType: e.pointerType,
type: e.type,
screenX: e.screenX,
screenY: e.screenY,
tiltX: e.tiltX,
tiltY: e.tiltY,
x: e.x,
y: e.y,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
isPrimary: e.isPrimary,
isTrusted: e.isTrusted,
metaKey: e.metaKey,
pressure: e.pressure,
returnValue: e.returnValue,
shiftKey: e.shiftKey,
timeStamp: e.timeStamp,
which: e.which,
twist: e.twist,
tangentialPressure: e.tangentialPressure,
target: e.target,
srcElement: e.srcElement,
relatedTarget: e.relatedTarget,
rangeOffset: e.rangeOffset,
rangeParent: e.rangeParent,
explicitOriginalTarget: e.explicitOriginalTarget,
eventPhase: e.eventPhase,
detail: e.detail,
defaultPrevented: e.defaultPrevented,
currentTarget: e.currentTarget,
buttons: e.buttons,
azimuthAngle: e.azimuthAngle,
altitudeAngle: e.altitudeAngle,
};
return {};
}