mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
improve code formatting and enhance readability in auto-redact.html, RedactionService, TextDecodingHelper, TextEncodingHelper, and TextFinder
Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
parent
ebe17f4c93
commit
e64bbebfd5
@ -3,8 +3,6 @@ package stirling.software.SPDF.pdf;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
@ -71,26 +69,33 @@ public class TextFinder extends PDFTextStripper {
|
|||||||
super.endPage(page);
|
super.endPage(page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String regex = this.useRegex ? processedSearchTerm : "\\Q" + processedSearchTerm + "\\E";
|
// Build patterns using unified utility for consistency
|
||||||
if (this.wholeWordSearch) {
|
List<java.util.regex.Pattern> patterns =
|
||||||
if (processedSearchTerm.length() == 1
|
stirling.software.SPDF.utils.text.TextFinderUtils.createOptimizedSearchPatterns(
|
||||||
&& Character.isDigit(processedSearchTerm.charAt(0))) {
|
java.util.Collections.singleton(processedSearchTerm),
|
||||||
regex = "(?<![\\w])(?<!\\d[\\.,])" + regex + "(?![\\w])(?![\\.,]\\d)";
|
this.useRegex,
|
||||||
} else if (processedSearchTerm.length() == 1) {
|
this.wholeWordSearch);
|
||||||
regex = "(?<![\\w])" + regex + "(?![\\w])";
|
java.util.regex.Matcher matcher = null;
|
||||||
} else {
|
java.util.regex.Pattern activePattern = null;
|
||||||
regex = "\\b" + regex + "\\b";
|
for (java.util.regex.Pattern p : patterns) {
|
||||||
|
matcher = p.matcher(text);
|
||||||
|
if (matcher
|
||||||
|
.find()) { // prime by checking has at least one match; we will re-iterate below
|
||||||
|
activePattern = p;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (activePattern == null) {
|
||||||
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
super.endPage(page);
|
||||||
Matcher matcher = pattern.matcher(text);
|
return;
|
||||||
|
}
|
||||||
|
matcher = activePattern.matcher(text);
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Searching for '{}' in page {} with regex '{}' (wholeWord: {}, useRegex: {})",
|
"Searching for '{}' in page {} with pattern '{}' (wholeWord: {}, useRegex: {})",
|
||||||
processedSearchTerm,
|
processedSearchTerm,
|
||||||
getCurrentPageNo(),
|
getCurrentPageNo(),
|
||||||
regex,
|
activePattern,
|
||||||
wholeWordSearch,
|
wholeWordSearch,
|
||||||
useRegex);
|
useRegex);
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ public class RedactionService {
|
|||||||
private static final Set<String> TEXT_SHOWING_OPERATORS = Set.of("Tj", "TJ", "'", "\"");
|
private static final Set<String> TEXT_SHOWING_OPERATORS = Set.of("Tj", "TJ", "'", "\"");
|
||||||
private static final COSString EMPTY_COS_STRING = new COSString("");
|
private static final COSString EMPTY_COS_STRING = new COSString("");
|
||||||
private static final int MAX_SWEEPS = 3;
|
private static final int MAX_SWEEPS = 3;
|
||||||
private static final Pattern PATTERN = Pattern.compile(".*(hoepap|temp|generated).*");
|
private static final Pattern PATTERN = Pattern.compile(".*(placeholder|temp|generated).*");
|
||||||
private boolean aggressiveMode = false;
|
private boolean aggressiveMode = false;
|
||||||
private Map<Integer, List<AggressiveSegMatch>> aggressiveSegMatches = null;
|
private Map<Integer, List<AggressiveSegMatch>> aggressiveSegMatches = null;
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
@ -2409,11 +2409,9 @@ public class RedactionService {
|
|||||||
textSegments.indexOf(task.segment), Collections.emptyList());
|
textSegments.indexOf(task.segment), Collections.emptyList());
|
||||||
|
|
||||||
if (task.segment.tokenIndex >= newTokens.size()) {
|
if (task.segment.tokenIndex >= newTokens.size()) {
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (task.segment.getText() == null || task.segment.getText().isEmpty()) {
|
if (task.segment.getText() == null || task.segment.getText().isEmpty()) {
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2430,19 +2428,22 @@ public class RedactionService {
|
|||||||
|
|
||||||
private static String extractStringWithFallbacks(COSString cosString, PDFont font) {
|
private static String extractStringWithFallbacks(COSString cosString, PDFont font) {
|
||||||
if (cosString == null) return "";
|
if (cosString == null) return "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Prefer font-guided decoding for correctness
|
||||||
|
if (font != null) {
|
||||||
|
String enhanced =
|
||||||
|
TextDecodingHelper.decodeCharactersEnhanced(font, cosString.getBytes());
|
||||||
|
if (enhanced != null && !isGibberish(enhanced)) return enhanced;
|
||||||
|
}
|
||||||
|
// Fallback to COSString raw string if it seems valid
|
||||||
String text = cosString.getString();
|
String text = cosString.getString();
|
||||||
if (!text.trim().isEmpty() && !isGibberish(text)) return text;
|
if (!text.trim().isEmpty() && !isGibberish(text)) return text;
|
||||||
|
// Fallback: try basic font-based extraction
|
||||||
if (font != null) {
|
if (font != null) {
|
||||||
String fontBasedText = tryFontBasedExtraction(cosString, font);
|
String fontBasedText = tryFontBasedExtraction(cosString, font);
|
||||||
if (fontBasedText != null && !isGibberish(fontBasedText)) return fontBasedText;
|
if (fontBasedText != null && !isGibberish(fontBasedText)) return fontBasedText;
|
||||||
}
|
}
|
||||||
|
// Last resort: sanitized raw
|
||||||
String encodingFallback = tryEncodingFallbacks(cosString);
|
|
||||||
if (encodingFallback != null && !isGibberish(encodingFallback)) return encodingFallback;
|
|
||||||
|
|
||||||
return sanitizeText(text);
|
return sanitizeText(text);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return "\uFFFD";
|
return "\uFFFD";
|
||||||
|
@ -123,9 +123,8 @@ public class TextDecodingHelper {
|
|||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
if (ch == null || !isPrintable(ch)) {
|
if (ch == null) {
|
||||||
// Handle problematic character codes specifically
|
return null; // fail fast if undecodable via font tables
|
||||||
ch = "<EFBFBD>";
|
|
||||||
}
|
}
|
||||||
out.append(ch);
|
out.append(ch);
|
||||||
i += consumed;
|
i += consumed;
|
||||||
@ -250,16 +249,8 @@ public class TextDecodingHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String handleProblematicCharacterCode(int code, PDFont font) {
|
public String handleProblematicCharacterCode(int code, PDFont font) {
|
||||||
if (code >= PROBLEMATIC_CODE_LOWER_BOUND && code <= PROBLEMATIC_CODE_UPPER_BOUND) {
|
// For correctness, avoid speculative remapping. Return replacement char only when needed.
|
||||||
int adjustedCode = code - PROBLEMATIC_CODE_LOWER_BOUND;
|
return "\uFFFD";
|
||||||
if (adjustedCode >= ASCII_LOWER_BOUND) {
|
|
||||||
return String.valueOf((char) adjustedCode);
|
|
||||||
}
|
|
||||||
if (font != null && font.getName() != null && font.getName().contains("+")) {
|
|
||||||
return mapSubsetCharacter(adjustedCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "<EFBFBD>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String mapSubsetCharacter(int code) {
|
public String mapSubsetCharacter(int code) {
|
||||||
@ -267,7 +258,8 @@ public class TextDecodingHelper {
|
|||||||
return String.valueOf((char) code);
|
return String.valueOf((char) code);
|
||||||
}
|
}
|
||||||
if (code >= EXTENDED_ASCII_LOWER_BOUND && code <= EXTENDED_ASCII_UPPER_BOUND) {
|
if (code >= EXTENDED_ASCII_LOWER_BOUND && code <= EXTENDED_ASCII_UPPER_BOUND) {
|
||||||
return String.valueOf((char) (code - 128));
|
// Do not alter code point arbitrarily; extended ASCII maps directly for correctness.
|
||||||
|
return String.valueOf((char) code);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -20,80 +20,44 @@ public class TextEncodingHelper {
|
|||||||
if (font == null || text == null) {
|
if (font == null || text == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Strict: every code point must be encodable by the font
|
||||||
try {
|
for (int i = 0; i < text.length(); ) {
|
||||||
byte[] encoded = font.encode(text);
|
int cp = text.codePointAt(i);
|
||||||
if (encoded.length > 0) {
|
String ch = new String(Character.toChars(cp));
|
||||||
return true;
|
try {
|
||||||
|
byte[] encoded = font.encode(ch);
|
||||||
|
if (encoded == null || encoded.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
i += Character.charCount(cp);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
return validateAsCodePointArray(font, text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateAsCodePointArray(PDFont font, String text) {
|
private boolean validateAsCodePointArray(PDFont font, String text) {
|
||||||
if (text == null || text.isEmpty()) {
|
if (text == null || text.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalCodePoints = 0;
|
|
||||||
int successfulCodePoints = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < text.length(); ) {
|
for (int i = 0; i < text.length(); ) {
|
||||||
int codePoint = text.codePointAt(i);
|
int codePoint = text.codePointAt(i);
|
||||||
String charStr = new String(Character.toChars(codePoint));
|
String charStr = new String(Character.toChars(codePoint));
|
||||||
totalCodePoints++;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] charEncoded = font.encode(charStr);
|
byte[] charEncoded = font.encode(charStr);
|
||||||
if (charEncoded.length > 0) {
|
if (charEncoded == null || charEncoded.length == 0) {
|
||||||
try {
|
return false;
|
||||||
float charWidth = font.getStringWidth(charStr);
|
|
||||||
if (charWidth >= 0) {
|
|
||||||
successfulCodePoints++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
if (canDecodeCharacter(font, charStr)) {
|
|
||||||
successfulCodePoints++;
|
|
||||||
}
|
|
||||||
} catch (Exception e2) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (canDecodeCharacter(font, charStr)) {
|
|
||||||
successfulCodePoints++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
try {
|
return false;
|
||||||
if (canDecodeCharacter(font, charStr)) {
|
|
||||||
successfulCodePoints++;
|
|
||||||
}
|
|
||||||
} catch (Exception e2) {
|
|
||||||
if (isBasicCharacter(codePoint)) {
|
|
||||||
successfulCodePoints++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i += Character.charCount(codePoint);
|
i += Character.charCount(codePoint);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
if (totalCodePoints == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
double successRate = (double) successfulCodePoints / totalCodePoints;
|
|
||||||
return successRate >= 0.1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canDecodeCharacter(PDFont font, String charStr) {
|
private boolean canDecodeCharacter(PDFont font, String charStr) {
|
||||||
@ -128,26 +92,17 @@ public class TextEncodingHelper {
|
|||||||
if (font == null || text == null) {
|
if (font == null || text == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Strict: removable only if we can encode every codepoint and measure width
|
||||||
if (isSimpleCharacter(text)) {
|
if (!canEncodeCharacters(font, text)) return false;
|
||||||
try {
|
try {
|
||||||
font.encode(text);
|
font.getStringWidth(text);
|
||||||
font.getStringWidth(text);
|
return true;
|
||||||
return true;
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
return false;
|
||||||
try {
|
|
||||||
return canHandleText(font, text);
|
|
||||||
} catch (Exception e2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isTextFullyRemovable(font, text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canHandleText(PDFont font, String text) {
|
private boolean canHandleText(PDFont font, String text) {
|
||||||
@ -197,68 +152,14 @@ public class TextEncodingHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTextFullyRemovable(PDFont font, String text) {
|
public boolean isTextFullyRemovable(PDFont font, String text) {
|
||||||
if (font == null || text == null) {
|
if (font == null || text == null) return false;
|
||||||
return false;
|
if (text.isEmpty()) return true;
|
||||||
}
|
if (!canEncodeCharacters(font, text)) return false;
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!canEncodeCharacters(font, text)) {
|
float width = font.getStringWidth(text);
|
||||||
return false;
|
return width >= 0;
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
float width = font.getStringWidth(text);
|
|
||||||
if (width < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
if (!canCalculateTextWidth(font, text)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (font.getFontDescriptor() == null) {
|
|
||||||
try {
|
|
||||||
return canHandleWithoutDescriptor(font, text);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
return canHandleWithoutDescriptor(font, text);
|
|
||||||
} catch (Exception e2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
font.getFontDescriptor().getFontBoundingBox();
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
return canHandleWithoutBoundingBox(font, text);
|
|
||||||
} catch (Exception e2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
try {
|
return false;
|
||||||
return canHandleText(font, text);
|
|
||||||
} catch (Exception e2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,45 +282,19 @@ public class TextEncodingHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean fontSupportsCharacter(PDFont font, String character) {
|
public boolean fontSupportsCharacter(PDFont font, String character) {
|
||||||
if (font == null || character == null) {
|
if (font == null || character == null) return false;
|
||||||
return false;
|
if (character.isEmpty()) return true;
|
||||||
}
|
|
||||||
|
|
||||||
if (character.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] encoded = font.encode(character);
|
|
||||||
if (encoded.length > 0) {
|
|
||||||
try {
|
|
||||||
float width = font.getStringWidth(character);
|
|
||||||
if (width >= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (canDecodeCharacter(font, character)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < character.length(); ) {
|
for (int i = 0; i < character.length(); ) {
|
||||||
int codePoint = character.codePointAt(i);
|
int cp = character.codePointAt(i);
|
||||||
if (isBasicCharacter(codePoint)) {
|
String ch = new String(Character.toChars(cp));
|
||||||
i += Character.charCount(codePoint);
|
try {
|
||||||
continue;
|
byte[] encoded = font.encode(ch);
|
||||||
|
if (encoded == null || encoded.length == 0) return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
i += Character.charCount(cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,321 +3,324 @@
|
|||||||
xmlns:th="https://www.thymeleaf.org">
|
xmlns:th="https://www.thymeleaf.org">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title}, header=#{autoRedact.header})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title}, header=#{autoRedact.header})}"></th:block>
|
||||||
<style>
|
<style>
|
||||||
.redaction-options-group {
|
.redaction-options-group {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-text.text-muted {
|
.form-text.text-muted {
|
||||||
color: #6c757d !important;
|
color: #6c757d !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:focus, .form-check-input:focus, .form-control:focus, .form-select:focus {
|
.btn-primary:focus, .form-check-input:focus, .form-control:focus, .form-select:focus {
|
||||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||||
outline: 2px solid #0d6efd;
|
outline: 2px solid #0d6efd;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check-input:checked {
|
.form-check-input:checked {
|
||||||
background-color: #0d6efd;
|
background-color: #0d6efd;
|
||||||
border-color: #0d6efd;
|
border-color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OCR language list styling */
|
/* OCR language list styling */
|
||||||
#languages {
|
#languages {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid var(--md-sys-color-surface-3);
|
border: 1px solid var(--md-sys-color-surface-3);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Better visibility for selected redaction option */
|
/* Better visibility for selected redaction option */
|
||||||
.redaction-options-group .form-check {
|
.redaction-options-group .form-check {
|
||||||
border: 1px solid var(--md-sys-color-surface-3);
|
border: 1px solid var(--md-sys-color-surface-3);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
|
transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redaction-options-group .form-check + .form-check { margin-top: .5rem; }
|
.redaction-options-group .form-check + .form-check { margin-top: .5rem; }
|
||||||
|
|
||||||
.redaction-options-group .form-check:hover {
|
.redaction-options-group .form-check:hover {
|
||||||
background-color: var(--md-sys-color-surface-1);
|
background-color: var(--md-sys-color-surface-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.redaction-options-group .form-check.selected {
|
.redaction-options-group .form-check.selected {
|
||||||
border-color: #0d6efd;
|
border-color: #0d6efd;
|
||||||
background-color: rgba(13,110,253,0.06);
|
background-color: rgba(13,110,253,0.06);
|
||||||
box-shadow: 0 0 0 2px rgba(13,110,253,0.1) inset;
|
box-shadow: 0 0 0 2px rgba(13,110,253,0.1) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redaction-options-group .form-check .form-check-label {
|
.redaction-options-group .form-check .form-check-label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redaction-options-group small.form-text {
|
.redaction-options-group small.form-text {
|
||||||
margin-left: 1.8rem; /* align with radio */
|
margin-left: 1.8rem; /* align with radio */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||||
<br><br>
|
<br><br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6 bg-card">
|
<div class="col-md-6 bg-card">
|
||||||
<div class="tool-header">
|
<div class="tool-header">
|
||||||
<svg class="material-symbols-rounded tool-header-icon security">
|
<svg class="material-symbols-rounded tool-header-icon security">
|
||||||
<use xlink:href="/images/redact-auto.svg#icon-redact-auto"></use>
|
<use xlink:href="/images/redact-auto.svg#icon-redact-auto"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="tool-header-text" id="form-title" th:text="#{autoRedact.header}"></span>
|
<span class="tool-header-text" id="form-title" th:text="#{autoRedact.header}"></span>
|
||||||
</div>
|
</div>
|
||||||
<form aria-labelledby="form-title" enctype="multipart/form-data" id="autoRedactForm"
|
<form aria-labelledby="form-title" enctype="multipart/form-data" id="autoRedactForm"
|
||||||
method="post" th:action="@{'api/v1/security/auto-redact'}">
|
method="post" th:action="@{'api/v1/security/auto-redact'}">
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}"></div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="listOfText" th:text="#{autoRedact.textsToRedactLabel}"></label>
|
<label class="form-label" for="listOfText" th:text="#{autoRedact.textsToRedactLabel}"></label>
|
||||||
<textarea class="form-control" id="listOfText" name="listOfText" required rows="4"
|
<textarea class="form-control" id="listOfText" name="listOfText" required rows="4"
|
||||||
th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
|
th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" id="useRegex" name="useRegex" type="checkbox">
|
<input class="form-check-input" id="useRegex" name="useRegex" type="checkbox">
|
||||||
<label class="form-check-label" for="useRegex" th:text="#{autoRedact.useRegexLabel}"></label>
|
<label class="form-check-label" for="useRegex" th:text="#{autoRedact.useRegexLabel}"></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" id="wholeWordSearch" name="wholeWordSearch" type="checkbox">
|
<input class="form-check-input" id="wholeWordSearch" name="wholeWordSearch" type="checkbox">
|
||||||
<label class="form-check-label" for="wholeWordSearch" th:text="#{autoRedact.wholeWordSearchLabel}"></label>
|
<label class="form-check-label" for="wholeWordSearch" th:text="#{autoRedact.wholeWordSearchLabel}"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="redaction-options-group">
|
<div class="redaction-options-group">
|
||||||
<label class="form-label fw-bold mb-3" th:text="#{autoRedact.redactionStyleLabel}"></label>
|
<label class="form-label fw-bold mb-3" th:text="#{autoRedact.redactionStyleLabel}"></label>
|
||||||
<div class="form-check mb-2">
|
<div class="form-check mb-2">
|
||||||
<input aria-describedby="visual-desc" checked class="form-check-input" id="visualImage" name="redactionMode" type="radio" value="visual">
|
<input aria-describedby="visual-desc" checked class="form-check-input" id="visualImage" name="redactionMode" type="radio" value="visual">
|
||||||
<label class="form-check-label" for="visualImage" th:text="#{autoRedact.visualRedactionLabel}">Visual</label>
|
<label class="form-check-label" for="visualImage" th:text="#{autoRedact.visualRedactionLabel}">Visual</label>
|
||||||
<small class="form-text text-muted d-block mt-1" id="visual-desc" th:text="#{autoRedact.visualRedactionDescription}">Converts to image with visual redactions for maximum security.</small>
|
<small class="form-text text-muted d-block mt-1" id="visual-desc" th:text="#{autoRedact.visualRedactionDescription}">Converts to image with visual redactions for maximum security.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mb-2">
|
<div class="form-check mb-2">
|
||||||
<input aria-describedby="delete-desc" class="form-check-input" id="deleteText" name="redactionMode" type="radio" value="aggressive">
|
<input aria-describedby="delete-desc" class="form-check-input" id="deleteText" name="redactionMode" type="radio" value="aggressive">
|
||||||
<label class="form-check-label" for="deleteText" th:text="#{autoRedact.deleteTextLabel}">Remove Text</label>
|
<label class="form-check-label" for="deleteText" th:text="#{autoRedact.deleteTextLabel}">Remove Text</label>
|
||||||
<small class="form-text text-muted d-block mt-1" id="delete-desc" th:text="#{autoRedact.deleteTextDescription}">Removes text completely, allowing the surrounding content to shift. This may change the document's original appearance.</small>
|
<small class="form-text text-muted d-block mt-1" id="delete-desc" th:text="#{autoRedact.deleteTextDescription}">Removes text completely, allowing the surrounding content to shift. This may change the document's original appearance.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input aria-describedby="keep-desc" class="form-check-input" id="keepLayout" name="redactionMode" type="radio" value="moderate">
|
<input aria-describedby="keep-desc" class="form-check-input" id="keepLayout" name="redactionMode" type="radio" value="moderate">
|
||||||
<label class="form-check-label" for="keepLayout" th:text="#{autoRedact.keepLayoutLabel}">Remove Text & Cover (Preserve Layout)</label>
|
<label class="form-check-label" for="keepLayout" th:text="#{autoRedact.keepLayoutLabel}">Remove Text & Cover (Preserve Layout)</label>
|
||||||
<small class="form-text text-muted d-block mt-1" id="keep-desc" th:text="#{autoRedact.keepLayoutDescription}">Removes the underlying text and places a redaction box in its place, preserving the document's original layout.</small>
|
<small class="form-text text-muted d-block mt-1" id="keep-desc" th:text="#{autoRedact.keepLayoutDescription}">Removes the underlying text and places a redaction box in its place, preserving the document's original layout.</small>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-check">
|
<!-- PDF Image checkbox - moved outside redaction-options-group to use plain styling -->
|
||||||
<input aria-describedby="guarantee-desc" class="form-check-input" id="guaranteeRedaction" name="convertPDFToImage" type="checkbox">
|
<div class="mb-3">
|
||||||
<label class="form-check-label" for="guaranteeRedaction" th:text="#{autoRedact.pdfImageLabel}">PDF image</label>
|
<div class="form-check">
|
||||||
<small class="form-text text-muted d-block mt-1" id="guarantee-desc" th:text="#{autoRedact.pdfImageDescription}">For maximum security, uses an image-based method to ensure text is unrecoverable. May slightly affect document quality.</small>
|
<input aria-describedby="guarantee-desc" class="form-check-input" id="convertPDFToImage" name="convertPDFToImage" type="checkbox">
|
||||||
</div>
|
<label class="form-check-label" for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}">Convert PDF to PDF-Image</label>
|
||||||
</div>
|
</div>
|
||||||
|
<small class="form-text text-muted d-block mt-1" id="guarantee-desc" th:text="#{autoRedact.pdfImageDescription}">For maximum security, uses an image-based method to ensure text is unrecoverable. May slightly affect document quality.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="defaultColor" th:text="#{autoRedact.colorLabel}"></label>
|
<label class="form-label" for="defaultColor" th:text="#{autoRedact.colorLabel}"></label>
|
||||||
<select class="form-select" id="defaultColor" name="defaultColor" onchange="handleColorChange(this.value)">
|
<select class="form-select" id="defaultColor" name="defaultColor" onchange="handleColorChange(this.value)">
|
||||||
<option th:text="#{black}" value="#000000">Black</option>
|
<option th:text="#{black}" value="#000000">Black</option>
|
||||||
<option th:text="#{white}" value="#FFFFFF">White</option>
|
<option th:text="#{white}" value="#FFFFFF">White</option>
|
||||||
<option th:text="#{red}" value="#FF0000">Red</option>
|
<option th:text="#{red}" value="#FF0000">Red</option>
|
||||||
<option th:text="#{green}" value="#00FF00">Green</option>
|
<option th:text="#{green}" value="#00FF00">Green</option>
|
||||||
<option th:text="#{blue}" value="#0000FF">Blue</option>
|
<option th:text="#{blue}" value="#0000FF">Blue</option>
|
||||||
<option th:text="#{custom}" value="custom">Custom...</option>
|
<option th:text="#{custom}" value="custom">Custom...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3" id="customColorContainer" style="display: none;">
|
<div class="mb-3" id="customColorContainer" style="display: none;">
|
||||||
<label class="form-label" for="customColor">Custom Color (Hex)</label>
|
<label class="form-label" for="customColor">Custom Color (Hex)</label>
|
||||||
<input class="form-control" id="customColor" name="redactColor" placeholder="#FF00FF" type="text">
|
<input class="form-control" id="customColor" name="redactColor" placeholder="#FF00FF" type="text">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="customPadding" th:text="#{autoRedact.customPaddingLabel}"></label>
|
<label class="form-label" for="customPadding" th:text="#{autoRedact.customPaddingLabel}"></label>
|
||||||
<input class="form-control" id="customPadding" max="1" min="0" name="customPadding"
|
<input class="form-control" id="customPadding" max="1" min="0" name="customPadding"
|
||||||
step="0.1" type="number" value="0.1">
|
step="0.1" type="number" value="0.1">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="mb-3" th:if="${#lists.size(languages) > 0}">
|
<div class="mb-3" th:if="${#lists.size(languages) > 0}">
|
||||||
<label class="form-label" for="languages">OCR Languages</label>
|
<label class="form-label" for="languages">OCR Languages</label>
|
||||||
<div id="languages">
|
<div id="languages">
|
||||||
<div class="form-check" th:each="language, iterStat : ${languages}">
|
<div class="form-check" th:each="language, iterStat : ${languages}">
|
||||||
<input onchange="handleLangSelection()" required th:checked="${language == 'eng'}" th:id="${'language-' + language}" th:name="languages" th:value="${language}" type="checkbox" />
|
<input class="form-check-input" onchange="handleLangSelection()" required th:checked="${language == 'eng'}" th:id="${'language-' + language}" th:name="languages" th:value="${language}" type="checkbox" />
|
||||||
<label th:attr="data-lang-code=${language}, data-lang-name=#{'lang.' + language}"
|
<label class="form-check-label" th:attr="data-lang-code=${language}, data-lang-name=#{'lang.' + language}"
|
||||||
th:for="${'language-' + language}"
|
th:for="${'language-' + language}"
|
||||||
th:text="${language}"></label>
|
th:text="${language}"></label>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted" id="ocr-desc">Used when OCR restoration is needed</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input id="aggressiveMode" name="aggressiveMode" type="hidden" value="false">
|
|
||||||
|
|
||||||
<div class="mb-3 text-center">
|
|
||||||
<button class="btn btn-primary" id="submitBtn" th:text="#{autoRedact.submitButton}" type="submit"></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted" id="ocr-desc">Used when OCR restoration is needed</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input id="aggressiveMode" name="aggressiveMode" type="hidden" value="false">
|
||||||
|
|
||||||
|
<div class="mb-3 text-center">
|
||||||
|
<button class="btn btn-primary" id="submitBtn" th:text="#{autoRedact.submitButton}" type="submit"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
</div>
|
||||||
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
</div>
|
</div>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
function handleColorChange(selectedValue) {
|
function handleColorChange(selectedValue) {
|
||||||
const container = document.getElementById('customColorContainer');
|
const container = document.getElementById('customColorContainer');
|
||||||
const input = document.getElementById('customColor');
|
const input = document.getElementById('customColor');
|
||||||
if (selectedValue === "custom") {
|
if (selectedValue === "custom") {
|
||||||
container.style.display = 'block';
|
container.style.display = 'block';
|
||||||
if (!input.value) {
|
if (!input.value) {
|
||||||
input.value = '#000000';
|
input.value = '#000000';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
container.style.display = 'none';
|
container.style.display = 'none';
|
||||||
input.value = selectedValue;
|
input.value = selectedValue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleLangSelection() {
|
function handleLangSelection() {
|
||||||
let checkboxes = document.getElementsByName("languages");
|
let checkboxes = document.getElementsByName("languages");
|
||||||
let selected = false;
|
let selected = false;
|
||||||
for (let i = 0; i < checkboxes.length; i++) {
|
for (let i = 0; i < checkboxes.length; i++) {
|
||||||
if (checkboxes[i].checked) {
|
if (checkboxes[i].checked) {
|
||||||
selected = true;
|
selected = true;
|
||||||
checkboxes[i].setAttribute('required', 'false');
|
checkboxes[i].setAttribute('required', 'false');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selected) {
|
if (selected) {
|
||||||
for (let i = 0; i < checkboxes.length; i++) {
|
for (let i = 0; i < checkboxes.length; i++) {
|
||||||
checkboxes[i].removeAttribute('required');
|
checkboxes[i].removeAttribute('required');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < checkboxes.length; i++) {
|
for (let i = 0; i < checkboxes.length; i++) {
|
||||||
checkboxes[i].setAttribute('required', 'true');
|
checkboxes[i].setAttribute('required', 'true');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language translations map populated by Thymeleaf for available OCR languages
|
// Language translations map populated by Thymeleaf for available OCR languages
|
||||||
const languageTranslations = {};
|
const languageTranslations = {};
|
||||||
/*[# th:each="lang : ${languages}"]*/
|
/*[# th:each="lang : ${languages}"]*/
|
||||||
languageTranslations['[(${lang})]'] = /*[[#{${'lang.' + lang}}]]*/'[(${lang})]';
|
languageTranslations['[(${lang})]'] = /*[[#{${'lang.' + lang}}]]*/'[(${lang})]';
|
||||||
/*[/]*/
|
/*[/]*/
|
||||||
|
|
||||||
const localeToTesseract = {
|
const localeToTesseract = {
|
||||||
'en': 'eng', 'fr': 'fra', 'de': 'deu', 'es': 'spa', 'it': 'ita', 'pt': 'por', 'ru': 'rus',
|
'en': 'eng', 'fr': 'fra', 'de': 'deu', 'es': 'spa', 'it': 'ita', 'pt': 'por', 'ru': 'rus',
|
||||||
'zh': 'chi_sim', 'ja': 'jpn', 'ko': 'kor', 'ar': 'ara', 'hi': 'hin', 'nl': 'nld', 'cs': 'ces',
|
'zh': 'chi_sim', 'ja': 'jpn', 'ko': 'kor', 'ar': 'ara', 'hi': 'hin', 'nl': 'nld', 'cs': 'ces',
|
||||||
'pl': 'pol', 'tr': 'tur', 'uk': 'ukr', 'vi': 'vie', 'sv': 'swe', 'no': 'nor', 'fi': 'fin',
|
'pl': 'pol', 'tr': 'tur', 'uk': 'ukr', 'vi': 'vie', 'sv': 'swe', 'no': 'nor', 'fi': 'fin',
|
||||||
'da': 'dan', 'el': 'ell', 'he': 'heb', 'hu': 'hun', 'bg': 'bul', 'ro': 'ron', 'hr': 'hrv',
|
'da': 'dan', 'el': 'ell', 'he': 'heb', 'hu': 'hun', 'bg': 'bul', 'ro': 'ron', 'hr': 'hrv',
|
||||||
'sk': 'slk', 'id': 'ind', 'th': 'tha', 'sl': 'slv'
|
'sk': 'slk', 'id': 'ind', 'th': 'tha', 'sl': 'slv'
|
||||||
};
|
};
|
||||||
|
|
||||||
function getTranslatedLanguageName(shortCode) {
|
function getTranslatedLanguageName(shortCode) {
|
||||||
// Use Thymeleaf-provided map; fall back to code when translation missing
|
// Use Thymeleaf-provided map; fall back to code when translation missing
|
||||||
const name = languageTranslations[shortCode];
|
const name = languageTranslations[shortCode];
|
||||||
if (name && !/^\?{2,}.+\?{2,}$/.test(name)) return name;
|
if (name && !/^\?{2,}.+\?{2,}$/.test(name)) return name;
|
||||||
return shortCode;
|
return shortCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prioritizeLanguages() {
|
function prioritizeLanguages() {
|
||||||
const languageContainer = document.getElementById('languages');
|
const languageContainer = document.getElementById('languages');
|
||||||
if (!languageContainer) return;
|
if (!languageContainer) return;
|
||||||
const formChecks = Array.from(languageContainer.getElementsByClassName('form-check'));
|
const formChecks = Array.from(languageContainer.getElementsByClassName('form-check'));
|
||||||
if (formChecks.length === 0) return;
|
if (formChecks.length === 0) return;
|
||||||
formChecks.forEach(element => {
|
formChecks.forEach(element => {
|
||||||
const label = element.querySelector('label');
|
const label = element.querySelector('label');
|
||||||
if (label) {
|
if (label) {
|
||||||
const langCode = label.getAttribute('for').split('-')[1];
|
const langCode = label.getAttribute('for').split('-')[1];
|
||||||
// Always set from translations map; gracefully falls back to code
|
// Always set from translations map; gracefully falls back to code
|
||||||
label.textContent = getTranslatedLanguageName(langCode);
|
label.textContent = getTranslatedLanguageName(langCode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const browserLanguage = document.documentElement.lang || navigator.language || navigator.userLanguage;
|
const browserLanguage = document.documentElement.lang || navigator.language || navigator.userLanguage;
|
||||||
const uiLanguage = document.documentElement.getAttribute('data-language') || browserLanguage;
|
const uiLanguage = document.documentElement.getAttribute('data-language') || browserLanguage;
|
||||||
const primaryLanguageCode = (uiLanguage || '').split(/[-_]/)[0].toLowerCase();
|
const primaryLanguageCode = (uiLanguage || '').split(/[-_]/)[0].toLowerCase();
|
||||||
const tesseractPrimaryCode = localeToTesseract[primaryLanguageCode];
|
const tesseractPrimaryCode = localeToTesseract[primaryLanguageCode];
|
||||||
const priorityLanguages = [];
|
const priorityLanguages = [];
|
||||||
if (tesseractPrimaryCode) priorityLanguages.push(tesseractPrimaryCode);
|
if (tesseractPrimaryCode) priorityLanguages.push(tesseractPrimaryCode);
|
||||||
if (tesseractPrimaryCode !== 'eng') priorityLanguages.push('eng');
|
if (tesseractPrimaryCode !== 'eng') priorityLanguages.push('eng');
|
||||||
const sortedElements = formChecks.sort((a, b) => {
|
const sortedElements = formChecks.sort((a, b) => {
|
||||||
const aInput = a.querySelector('input');
|
const aInput = a.querySelector('input');
|
||||||
const bInput = b.querySelector('input');
|
const bInput = b.querySelector('input');
|
||||||
if (!aInput || !bInput) return 0;
|
if (!aInput || !bInput) return 0;
|
||||||
const aLangCode = aInput.id.split('-')[1];
|
const aLangCode = aInput.id.split('-')[1];
|
||||||
const bLangCode = bInput.id.split('-')[1];
|
const bLangCode = bInput.id.split('-')[1];
|
||||||
const aIsPriority = priorityLanguages.includes(aLangCode);
|
const aIsPriority = priorityLanguages.includes(aLangCode);
|
||||||
const bIsPriority = priorityLanguages.includes(bLangCode);
|
const bIsPriority = priorityLanguages.includes(bLangCode);
|
||||||
if (aIsPriority && !bIsPriority) return -1;
|
if (aIsPriority && !bIsPriority) return -1;
|
||||||
if (!aIsPriority && bIsPriority) return 1;
|
if (!aIsPriority && bIsPriority) return 1;
|
||||||
if (aIsPriority && bIsPriority) {
|
if (aIsPriority && bIsPriority) {
|
||||||
return priorityLanguages.indexOf(aLangCode) - priorityLanguages.indexOf(bLangCode);
|
return priorityLanguages.indexOf(aLangCode) - priorityLanguages.indexOf(bLangCode);
|
||||||
}
|
}
|
||||||
return getTranslatedLanguageName(aLangCode).localeCompare(getTranslatedLanguageName(bLangCode));
|
return getTranslatedLanguageName(aLangCode).localeCompare(getTranslatedLanguageName(bLangCode));
|
||||||
});
|
});
|
||||||
languageContainer.innerHTML = '';
|
languageContainer.innerHTML = '';
|
||||||
sortedElements.forEach(element => languageContainer.appendChild(element));
|
sortedElements.forEach(element => languageContainer.appendChild(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const redactionModeRadios = document.querySelectorAll('input[name="redactionMode"]');
|
const redactionModeRadios = document.querySelectorAll('input[name="redactionMode"]');
|
||||||
const aggressiveModeHidden = document.getElementById('aggressiveMode');
|
const aggressiveModeHidden = document.getElementById('aggressiveMode');
|
||||||
const guaranteeRedactionCheckbox = document.getElementById('guaranteeRedaction');
|
const convertPDFToImageCheckbox = document.getElementById('convertPDFToImage');
|
||||||
const defaultColor = document.getElementById('defaultColor');
|
const defaultColor = document.getElementById('defaultColor');
|
||||||
|
|
||||||
function updateMode() {
|
function updateMode() {
|
||||||
const selectedMode = document.querySelector('input[name="redactionMode"]:checked');
|
const selectedMode = document.querySelector('input[name="redactionMode"]:checked');
|
||||||
if (selectedMode) {
|
if (selectedMode) {
|
||||||
// Set aggressive mode for delete text option
|
// Set aggressive mode for delete text option
|
||||||
aggressiveModeHidden.value = selectedMode.value === 'aggressive' ? 'true' : 'false';
|
aggressiveModeHidden.value = selectedMode.value === 'aggressive' ? 'true' : 'false';
|
||||||
|
|
||||||
// Handle PDF image checkbox based on selection
|
// Handle PDF image checkbox based on selection
|
||||||
if (selectedMode.value === 'visual') {
|
if (selectedMode.value === 'visual') {
|
||||||
// Visual mode automatically enables PDF image for maximum security
|
// Visual mode automatically enables PDF image for maximum security
|
||||||
guaranteeRedactionCheckbox.checked = true;
|
convertPDFToImageCheckbox.checked = true;
|
||||||
} else {
|
} else {
|
||||||
// Delete Text and Keep Layout modes disable PDF image
|
// Delete Text and Keep Layout modes disable PDF image
|
||||||
guaranteeRedactionCheckbox.checked = false;
|
convertPDFToImageCheckbox.checked = false;
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight selected card
|
|
||||||
document.querySelectorAll('.redaction-options-group .form-check').forEach(div => div.classList.remove('selected'));
|
|
||||||
const parent = selectedMode.closest('.form-check');
|
|
||||||
if (parent) parent.classList.add('selected');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redactionModeRadios.forEach(radio => {
|
// Highlight selected card
|
||||||
radio.addEventListener('change', updateMode);
|
document.querySelectorAll('.redaction-options-group .form-check').forEach(div => div.classList.remove('selected'));
|
||||||
});
|
const parent = selectedMode.closest('.form-check');
|
||||||
|
if (parent) parent.classList.add('selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultColor) {
|
redactionModeRadios.forEach(radio => {
|
||||||
handleColorChange(defaultColor.value);
|
radio.addEventListener('change', updateMode);
|
||||||
const customColorInput = document.getElementById('customColor');
|
});
|
||||||
if (defaultColor.value !== 'custom') {
|
|
||||||
customColorInput.value = defaultColor.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMode();
|
if (defaultColor) {
|
||||||
|
handleColorChange(defaultColor.value);
|
||||||
|
const customColorInput = document.getElementById('customColor');
|
||||||
|
if (defaultColor.value !== 'custom') {
|
||||||
|
customColorInput.value = defaultColor.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMode();
|
||||||
|
|
||||||
// Initialize language list ordering & labels
|
// Initialize language list ordering & labels
|
||||||
prioritizeLanguages();
|
prioritizeLanguages();
|
||||||
|
|
||||||
// Handle pre-selected English language
|
// Handle pre-selected English language
|
||||||
handleLangSelection();
|
handleLangSelection();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue
Block a user