normalize_spot_color
Приложение для транслитерации и нормализации имен сепараций в PDF-файлах.

Оглавление:
• Вступление
• Первые варианты и почему я отказался от Enfocus Pitstop
• Иcпользование как консольного приложения Java
• Иcпользование в Enfocus Switch
• Код normalize_spot_color-1.1.jar (Java)
• Код NormalizeSpotPDFbox.sscript (Enfocus Switch)
Вступление
Одна из распространенных проблем при работе с PDF-файлами — это нестандартизированность имен сепараций, особенно в файлах, полученных от сторонних заказчиков.
Опытные дизайнеры используют библиотеки Pantone, но менее опытные могут создавать спот-цвета вручную, что приводит к ошибкам, таким как использование кириллицы или неправильное написание. Это вызывает проблемы при обработке файлов на рипе и требует ручного вмешательства.
Первые варианты (или почему я отказался от Enfocus Pitstop)
Первый (рабочий) прототип
Первый шаг — создание action в Pitstop с использованием regex и метода Rename spot color. Этот метод частично рабочий, но не позволяет транслитерировать символы и гарантировать обработку всех сепараций за один прогон.
Если кому интересно, я использовал такой regex (при необходимости, файл обрабатывался до 10 раз в цикле):
Это: (?!Cyan$|Magenta$|Yellow$|Black$|None$|none$|All$|all$|ProofColor$)^.*?((?:[A-Za-z0-9()\- #_]+.*?)+)[^A-Za-z0-9()\- #_]*((?:[A-Za-z0-9()\- #_]+.*?)+)*$
Менялось на: $1_$2
После такой обработки не входящие в список "A-Za-z0-9()- #" символы просто заменялись на "_"
Метод "топорный". Работает. Но некрасиво. И не универсально. Эффективность данного метода я бы оценил в 80%, потому, что иногда бывают очень креативные дизайнеры, любящие необычные названия для своих цветов. И мне, человеку техническому, тяжело заранее предугадать их полет фантазии, а данный подход как раз и требует некоего предугадывания… А еще данный подход не позволяет реализовать нормализацию имен.
Второй (нерабочий) прототип
Следующий вариант предполагал использование Switch для анализа имен сепараций и передачи их в Pitstop. Однако метод Rename spot color в Pitstop не может получать имена сепараций из переменных, что делает этот подход нерабочим.
UPDATE
Начиная с Pitstop 24.11 появилась возможность передавать имена исходной и конечной сепараций в переменных в методе Rename spot color. Фактически это то, что надо. Но, к сожалению, владельцам взломанного Switch данная версия Pitstop Server пока недоступна.
Для переименования надо было бы использовать метод Remap spot color, он позволяет передать имена исходной и конечной сепараций в переменных, но также требует, чтобы были переданы цветовые координаты итоговой сепарации в какой-либо одной цветовой модели. А извлечь информацию о цвете сепарации — это огромная проблема, потому что эти данные зачастую записаны внутри как функция, описывающая цвет. И чтобы рассчитать итоговые значения, надо очень хорошо знать спецификацию стандарта PDF. Я пытался с этим разобраться, но решил, что у меня нет столько времени. Это выше моего потолка знаний. От данного подхода решено было отказаться.
Поиск альтернативы
После осознания, что простого решения в экосистеме Enfocus нет, было решено искать альтернативные библиотеки для работы с PDF. В Node.js и Python подходящих библиотек не нашлось, но в Java была найдена Apache PDFbox.
Apache PDFbox
Apache PDFbox — это библиотека с открытым исходным кодом на языке Java, которая позволяет создавать, рендерить, печатать, разделять, объединять, изменять, проверять и извлекать текст и метаданные из PDF-файлов. С её помощью было создано приложение "normalize_spot_color", которое позволяет транслитерировать и нормализовать имена сепараций.
Использование как консольного приложения Java
Приложение принимает один обязательный и пять вспомогательных параметров:
java -jar "Путь к файлу" "Разрешенные символы" "Символ замены" "Вспомогательная кодировка" "Путь к transliteration.json" "Нормализовывать имена"
Пример использования:
java -jar "d:\normalize_spot_color-1.0.jar" "d:\Test.pdf" "#()-_" "_" "windows-1251" "d:\transliteration.json" "All"
Путь к pdf файлу (обязательно) — путь к изменяемому файлу;
Разрешенные символы — по умолчанию, разрешены латинские буквы a-Z (в обоих регистрах), цифры 0-9 и пробелы, их указывать не надо. В этом параметре можно указать дополнительные символы, которые также можно использовать в названиях сепараций. Например "#()-_".
Если символ указан и в этом параметре, и далее в файле transliteration.json, то транслитерация производиться не будет.
Символ замены — символ, на который заменяются неразрешенные символы.
Если символ замены не указан или пуст, тогда неразрешенные символы будут просто удалены.
Вспомогательная кодировка — по умолчанию в PDF для хранения имен сепараций используется кодировка UTF-8. Но на самом деле, может использоваться любая другая, это зависит от программы, с помощью которой создана сепарация. Например, на компьютере с Windows при создании сепарации в pdf с кириллическими символами в названии при помощи Prinect PDF Toolbox, данное название кодируется при помощи кодировки "windows-1251".
Normalize_spot_color всегда сначала пытается прочитать название сепарации как закодированное с помощью UTF-8. Если же после внутренней проверки станет понятно, что использовалась не UTF-8, то для чтения строки будет использована указанная в данном параметре кодировка.
Если кодировка не указана, то будет использована UTF-8.
Путь к transliteration.json — можно создать свой словарь транслитерации, тогда символы, находящиеся в этом словаре будут замены не символом замены, а символом из словаря. По ссылке для скачивания есть пример с кириллицей.
Нормализовывать ли имена сепарации — варианты: <Off|All|Transliterated only>.
Off — не производить нормализацию, сепарации будут только транслитерированы и/или некоторые символы в них будут замены.
All — все имена сепараций будут нормализованы при необходимости.
Transliterated only — будут нормализованы только те сепарации, в которых были обнаружены и заменены нелегальные символы.
Данная опция позволяет нормализовать, то есть привести к единому виду, имена сепараций:
• слово PANTONE, если название сепарации начинается с него, всегда пишется в верхнем регистре и отделено от последующих цифр пробелом; 1-3 буквы, идущие после цифрового индекса и обозначающие стандарт цвета в пантоне — тоже всегда капсом и отделены пробелом ("PANTONE 931 CVU").
• eсли название сепарации начинается с цифр, то в начале автоматом добавляется слово PANTONE. А если после цифр нет буквенного индекса, добавляется "C".
• остальные слова всегда начинаются с заглавной буквы, а все последующие буквы в них — строчные.
Если опция опущена или указана неверно, нормализация не производится.
Использование в Enfocus Switch
Хотя приложение можно использовать как отдельный консольный продукт, изначально оно разрабатывалось для использования в Switch. Для удобства написано NormalizeSpotPDFbox.sscript

Все настройки вполне понятны и почти полностью дублируют настройки из normalize_spot_color.jar. Кроме одной, которую надо объяснить отдельно:
Path to Java JRE folder — если у вас установлена Java, то можно ничего здесь не указывать. Если же Java не установлена, то можно скачать Java JRE как zip-архив (например, отсюда), распаковать в какую-то папку и не устанавливать, а просто указать путь (внутри папки должна находиться подпапка bin).
normalize spot color
JAVA. Версия 1.1. Приложение собрано с помощью maven
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNull;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDShadingPattern;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPattern;
import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDDictionaryWrapper;
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.text.WordUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RenameSpotColor {
private static void handlePDResources(PDResources PDResources, String specialChars, String replacementChar,
String charsetReplacement, String normalizeSeparationName,
Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern) {
if (PDResources == null) {
return;
}
for (COSName name : PDResources.getColorSpaceNames()) {
PDColorSpace colorSpace;
try {
colorSpace = PDResources.getColorSpace(name);
handleColorSpace(colorSpace, specialChars, replacementChar, charsetReplacement, normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
} catch (IOException e) {
System.out.println("Error handling color space in PDResources: " + e.getMessage());
return; // Skip this color space and continue with the next
}
}
// PDResources XObjects
for (COSName name : PDResources.getXObjectNames()) {
PDXObject nestedXObject;
try {
nestedXObject = PDResources.getXObject(name);
handleXObject(nestedXObject, specialChars, replacementChar, charsetReplacement, normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
} catch (IOException e) {
System.out.println("Error handling XObjects in PDResources: " + e.getMessage());
return; // Skip this color space and continue with the next
}
}
// PDResources Shadings
for (COSName name : PDResources.getShadingNames()) {
PDShading shading;
try {
shading = PDResources.getShading(name);
handleShadings(shading, specialChars, replacementChar, charsetReplacement, normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
} catch (IOException e) {
System.out.println("Error handling shadings in PDResources: " + e.getMessage());
return; // Skip this color space and continue with the next
}
}
// PDResources Patterns
for (COSName name : PDResources.getPatternNames()) {
try {
PDAbstractPattern abstractPattern = PDResources.getPattern(name);
System.out.println("patternDictionary: " + abstractPattern);
handlePattern(abstractPattern, specialChars, replacementChar, charsetReplacement,
normalizeSeparationName, renamedSeparations, converter,
validCharsPattern);
} catch (IOException e) {
System.out.println("Error handling patterns in PDResources: " + e.getMessage());
return; // Skip this color space and continue with the next
}
}
}
private static void handlePattern(PDAbstractPattern abstractPattern, String specialChars, String replacementChar,
String charsetReplacement, String normalizeSeparationName,
Set<String> renamedSeparations, Map<Character, String> converter,
Pattern validCharsPattern) throws IOException {
if (abstractPattern instanceof PDTilingPattern) {
PDTilingPattern tilingPattern = (PDTilingPattern) abstractPattern;
for (COSName name : tilingPattern.getResources().getColorSpaceNames()) {
PDColorSpace colorSpace = tilingPattern.getResources().getColorSpace(name);
handleColorSpace(colorSpace, specialChars, replacementChar, charsetReplacement, normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
}
} else if (abstractPattern instanceof PDShadingPattern) {
PDShadingPattern shadingPattern = (PDShadingPattern) abstractPattern;
handleShadings(shadingPattern.getShading(), specialChars, replacementChar, charsetReplacement,
normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
}
}
private static void handleShadings(PDShading shading, String specialChars, String replacementChar, String charsetReplacement, String normalizeSeparationName, Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern) {
try {
PDColorSpace colorSpace = shading.getColorSpace();
handleColorSpace(colorSpace, specialChars, replacementChar, charsetReplacement, normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
} catch (IOException e) {
return; // Skip this color space and continue with the next
}
}
private static void handleXObject(PDXObject nestedXObject, String specialChars, String replacementChar, String charsetReplacement, String normalizeSeparationName, Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern)
throws IOException {
try {
if (nestedXObject instanceof PDFormXObject) {
COSDictionary resources = nestedXObject.getCOSObject();
PDFormXObject formXObject = (PDFormXObject) nestedXObject;
handlePDResources(formXObject.getResources(), specialChars, replacementChar, charsetReplacement, normalizeSeparationName, renamedSeparations, converter, validCharsPattern);
} else if (nestedXObject instanceof PDImageXObject) {
PDImageXObject imageXObject = (PDImageXObject) nestedXObject;
handleImageXObject(imageXObject, specialChars, replacementChar, charsetReplacement,
normalizeSeparationName,
renamedSeparations, converter, validCharsPattern);
}
} catch (Exception e) {
System.out.println("Error in handleXObject function: " + e.getMessage());
}
}
private static void handleImageXObject(PDImageXObject imageXObject, String specialChars, String replacementChar, String charsetReplacement, String normalizeSeparationName, Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern) {
try {
if (imageXObject.getColorSpace() == null) {
return;
}
PDColorSpace colorSpace = imageXObject.getColorSpace();
handleColorSpace(colorSpace, specialChars, replacementChar, charsetReplacement, normalizeSeparationName, renamedSeparations, converter, validCharsPattern);
} catch (IOException e) {
return;
}
}
private static void handleColorSpace(PDColorSpace colorSpace, String specialChars, String replacementChar, String charsetReplacement, String normalizeSeparationName, Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern) {
if (colorSpace instanceof PDSeparation) {
handleSeparation((PDSeparation) colorSpace, specialChars, replacementChar, charsetReplacement, normalizeSeparationName, renamedSeparations, converter, validCharsPattern);
} else if (colorSpace instanceof PDDeviceN) {
handleDeviceN((PDDeviceN) colorSpace, specialChars, replacementChar, charsetReplacement, normalizeSeparationName, renamedSeparations, converter, validCharsPattern);
}
}
private static void handleSeparation(PDSeparation cs, String specialChars, String replacementChar, String charsetReplacement, String normalizeSeparationName, Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern) {
String colorantName = cs.getColorantName();
String decodedColorantName = decodeIncorrectEncoding(colorantName, charsetReplacement);
if (needsRenaming(decodedColorantName, validCharsPattern)) {
String translit = translit(decodedColorantName, specialChars, replacementChar, converter);
translit = translit.isEmpty() ? (replacementChar.isEmpty() ? "_" : replacementChar) : translit;
renameColorant(cs, decodedColorantName, translit, normalizeSeparationName, renamedSeparations);
} else if (normalizeSeparationName.equals("All")) {
normalizeColorant(cs, decodedColorantName, renamedSeparations);
}
}
private static void handleDeviceN(PDDeviceN deviceN, String specialChars, String replacementChar, String charsetReplacement, String normalizeSeparationName, Set<String> renamedSeparations, Map<Character, String> converter, Pattern validCharsPattern) {
List<String> colorantNames = deviceN.getColorantNames();
for (int i = 0; i < colorantNames.size(); i++) {
String colorantName = colorantNames.get(i);
String decodedColorantName = decodeIncorrectEncoding(colorantName, charsetReplacement);
if (needsRenaming(decodedColorantName, validCharsPattern)) {
String translit = translit(decodedColorantName, specialChars, replacementChar, converter);
translit = translit.isEmpty() ? (replacementChar.isEmpty() ? "_" : replacementChar) : translit;
renameColorantInDeviceN(deviceN, colorantNames, i, decodedColorantName, translit,
normalizeSeparationName, renamedSeparations);
} else if (normalizeSeparationName.equals("All")) {
normalizeColorantInDeviceN(deviceN, colorantNames, i, decodedColorantName, renamedSeparations);
}
}
}
private static boolean needsRenaming(String decodedColorantName, Pattern validCharsPattern) {
Matcher matcher = validCharsPattern.matcher(decodedColorantName);
return !matcher.matches();
}
private static void renameColorant(PDSeparation cs, String decodedColorantName, String translit, String normalizeSeparationName, Set<String> renamedSeparations) {
String normalized = normalizeSeparationName(translit);
renamedSeparations.add(decodedColorantName + " -> " + normalized);
cs.setColorantName(normalized);
}
private static void normalizeColorant(PDSeparation cs, String decodedColorantName, Set<String> renamedSeparations) {
String normalized = normalizeSeparationName(decodedColorantName);
if (!normalized.equals(decodedColorantName)) {
renamedSeparations.add(decodedColorantName + " -> " + normalized);
cs.setColorantName(normalized);
}
}
private static void renameColorantInDeviceN(PDDeviceN deviceN, List<String> colorantNames, int index, String decodedColorantName, String translit, String normalizeSeparationName, Set<String> renamedSeparations) {
String normalized = normalizeSeparationName(translit);
renamedSeparations.add(decodedColorantName + " -> " + normalized);
colorantNames.set(index, normalized);
deviceN.setColorantNames(colorantNames);
updateColorantsDict(deviceN, decodedColorantName, translit);
}
private static void normalizeColorantInDeviceN(PDDeviceN deviceN, List<String> colorantNames, int index, String decodedColorantName, Set<String> renamedSeparations) {
String normalized = normalizeSeparationName(decodedColorantName);
if (!normalized.equals(decodedColorantName)) {
renamedSeparations.add(decodedColorantName + " -> " + normalized);
colorantNames.set(index, normalized);
deviceN.setColorantNames(colorantNames);
updateColorantsDict(deviceN, decodedColorantName, normalized);
}
}
private static void updateColorantsDict(PDDeviceN deviceN, String oldName, String newName) {
if (deviceN.isNChannel()) {
COSDictionary cosDictionary = deviceN.getAttributes().getCOSDictionary();
COSDictionary colorantsDict = (COSDictionary) cosDictionary
.getCOSDictionary(COSName.getPDFName("Colorants"));
if (colorantsDict != null) {
COSName oldKey = COSName.getPDFName(oldName);
COSName newKey = COSName.getPDFName(newName);
COSObject value = colorantsDict.getCOSObject(oldKey);
if (value != null) {
COSBase valueDict = value.getObject();
if (valueDict instanceof COSArray) {
COSArray cosArray = (COSArray) valueDict;
for (int i = 0; i < cosArray.size(); i++) {
COSBase item = cosArray.get(i);
if (item instanceof COSName && item.equals(oldKey)) {
cosArray.set(i, newKey);
break; // Replace the first occurrence
}
}
}
colorantsDict.removeItem(oldKey);
colorantsDict.setItem(newKey, value);
}
}
}
}
private static String normalizeSeparationName(String separationName) {
separationName = separationName.trim();
if (separationName.matches("^(?i)pr(.)?oo(.)?color$")) {
return "ProofColor";
}
separationName = separationName.replaceAll("(?i)panton(e)?(\\s)*", "PANTONE ");
if (separationName.matches("^[pP]\\s+.*")) {
separationName = separationName.replaceFirst("^[pP]\\s+", "PANTONE ");
} else if (separationName.matches("^[pP]\\d.*")) {
separationName = separationName.replaceFirst("^[pP]", "PANTONE ");
} else if (separationName.matches("^\\d.*")) {
separationName = "PANTONE " + separationName;
}
Pattern pantonePattern = Pattern
.compile("^(PANTONE\\s)([\\s\\-a-zA-Z]*)?(\\d+(-\\d+)?)?(\\s*+[a-zA-Z]{1,3})?(\\s+.*)?$");
Matcher matcher = pantonePattern.matcher(separationName);
if (matcher.matches()) {
String text = matcher.group(2);
String number = matcher.group(3);
String standard = matcher.group(5);
String additionalInfo = matcher.group(6);
StringBuilder result = new StringBuilder("PANTONE");
if (text != null) {
result.append(" ").append(WordUtils.capitalizeFully(text.trim()));
}
if (number != null) {
result.append(" ").append(number.trim());
}
if (standard != null) {
result.append(" ").append(standard.trim().toUpperCase());
} else if (standard == null && additionalInfo == null && number != null) {
result.append(" C");
}
if (additionalInfo != null) {
result.append(" ").append(WordUtils.capitalizeFully(additionalInfo.trim()));
}
return result.toString().replaceAll("\\s{2,}", " ");
} else {
return WordUtils.capitalizeFully(separationName.trim().replaceAll("\\s{2,}", " "));
}
}
private static String decodeIncorrectEncoding(String name, String charsetReplacement) {
if (isValidUtf8(name.getBytes(StandardCharsets.ISO_8859_1))) {
return name;
}
if (charsetReplacement == null || charsetReplacement.isEmpty() || !Charset.isSupported(charsetReplacement)) {
return name; // Return original if charsetReplacement is empty or unsupported
}
try {
byte[] bytes = name.getBytes(StandardCharsets.ISO_8859_1);
return new String(bytes, charsetReplacement);
} catch (Exception e) {
return name;
}
}
private static boolean isValidUtf8(byte[] bytes) {
try {
String str = new String(bytes, StandardCharsets.UTF_8);
return str.getBytes(StandardCharsets.UTF_8).length == bytes.length;
} catch (Exception e) {
return false;
}
}
private static String translit(String str, String specialChars, String replacementChar, Map<Character, String> converter) {
StringBuilder answer = new StringBuilder(str.length());
for (int i = 0; i < str.length(); i++) {
char charAt = str.charAt(i);
if (String.valueOf(charAt).matches("[ a-zA-Z0-9" + specialChars + "]")) {
answer.append(charAt);
} else {
String mappedValue = converter.get(charAt);
if (mappedValue != null) {
answer.append(mappedValue);
} else if (!replacementChar.isEmpty()) {
answer.append(replacementChar);
}
}
}
return answer.toString();
}
public static void main(String[] args) {
if (args.length == 0) {
System.err.println(
"Error: insufficient arguments. Expected 1 mandatory and 5 optional arguments: <inputFilePath> <specialChars> <replacementChar> <fallbackCharset> <transliterationJSONPath> <NormalizeSeparationName: Off|All|Transliterated only>");
return;
}
String inputFilePath = args[0];
String specialChars = args.length > 1 ? Pattern.quote(args[1]) : "";
String replacementChar = args.length > 2 ? args[2] : "";
String charsetReplacement = args.length > 3 ? args[3] : "UTF-8";
String transliterationJSONPath = args.length > 4 ? args[4] : "";
String normalizeSeparationName = args.length > 5 ? args[5] : "Off";
Pattern validCharsPattern = Pattern.compile("^[ a-zA-Z0-9" + specialChars + "]*$");
if (!normalizeSeparationName.equals("All") && !normalizeSeparationName.equals("Off")
&& !normalizeSeparationName.equals("Transliterated only")) {
normalizeSeparationName = "Off";
}
// boolean renamingOccurred = false;
Set<String> renamedSeparations = new HashSet<>();
Map<Character, String> converter = new HashMap<>();
File inputFile = new File(inputFilePath);
if (!inputFile.exists()) {
System.err.println("Error: input file not found, check path");
return;
}
File jsonFile = new File(transliterationJSONPath);
try {
if (jsonFile.exists()) {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> jsonMappings = objectMapper.readValue(jsonFile,
new TypeReference<Map<String, String>>() {
});
for (Map.Entry<String, String> entry : jsonMappings.entrySet()) {
converter.put(entry.getKey().charAt(0), entry.getValue());
}
}
} catch (IOException e) {
// If the file is not found or any other IO exception occurs, we will not throw
// an error. Just proceed with the specialChars
}
for (char specialChar : specialChars.toCharArray()) {
converter.put(specialChar, String.valueOf(specialChar));
}
try (PDDocument document = Loader.loadPDF(inputFile)) {
Objects.requireNonNull(document, "Error: document should not be null");
for (PDPage page : document.getPages()) {
Objects.requireNonNull(page, "Error: page should not be null");
if (page.getResources() == null) {
continue; // Skip this page if resources are not available
} else {
handlePDResources(page.getResources(), specialChars, replacementChar, charsetReplacement, normalizeSeparationName, renamedSeparations, converter, validCharsPattern);
}
}
if (!renamedSeparations.isEmpty()) {
File tempFile = new File(inputFilePath + ".tmp");
try {
document.save(tempFile);
Files.delete(inputFile.toPath());
Files.move(tempFile.toPath(), inputFile.toPath(),
StandardCopyOption.REPLACE_EXISTING);
System.out.println(String.join("; ", renamedSeparations));
} finally {
if (tempFile.exists()) {
tempFile.delete();
}
}
} else {
System.out.println("Renaming of separations not required. Original file remains unchanged.");
}
} catch (IOException e) {
System.err.println("Error processing the PDF file: " + e.getMessage());
} catch (NullPointerException e) {
System.err.println("Error: null pointer encountered. " + e.getMessage());
} catch (Exception e) {
System.err.println("Error: an unexpected error occurred. " + e.getMessage());
}
}
}
NormalizeSpotPDFbox.sscript
const { promisify } = require('util');
const { exec, execFile } = require('child_process');
const os = require('os');
const path = require('path');
const execPromise = promisify(exec);
const execFilePromise = promisify(execFile);
// Function to check if Java is installed
async function checkJavaInstallation() {
try {
await execPromise('java -version');
return true; // Java is installed
} catch (error) {
return false; // Java is not installed
}
}
// Function to run a Java command or a Java application
async function runJavaApp(command, jrePath, args = []) {
if (jrePath) {
const javaExecutable = os.platform() === 'win32' ? 'java.exe' : 'java'; // Check the OS
// Use the custom JRE
try {
return await execFilePromise(path.join(jrePath, 'bin', javaExecutable), [command, ...args]);
} catch (error) {
throw error;
}
} else {
// Check for a locally installed JRE
const isInstalled = await checkJavaInstallation();
if (isInstalled) {
try {
return await execFilePromise("java", [command, ...args]);
} catch (error) {
throw error;
} } else {
throw new Error('Java not installed. Please install Java or provide a custom JRE path.');
}
}
}
async function validateProperties(s, flowElement, tags) {
const jrePath = await flowElement.getPropertyStringValue("jrePath");
const replacementChar = await flowElement.getPropertyStringValue("replacementChar");
const regexpString = await flowElement.getPropertyStringValue("specialChars");
const regexp = new RegExp("^[ a-zA-Z0-9" + regexpString.replace(/[-.*+?^${}()|[\]\\=]/g, '\\$&') + "]*$");
const renameSpotColor = await flowElement.getPropertyStringValue("renameSpotColor");
const transliteration = await flowElement.getPropertyStringValue("transliteration");
let retval = [];
flowElement.log(LogLevel.Info, `Validating ${tags.length} properties...`);
for (let tag of tags) {
if (tag === "jrePath") {
if (jrePath === "") {
const isInstalled = await checkJavaInstallation();
if (isInstalled) {
retval.push({ tag: "jrePath", valid: true });
} else {
flowElement.log(LogLevel.Error, "No Java Runtime Environment found. Please install Java or provide a custom JRE path.");
retval.push({ tag: "jrePath", valid: false });
}
} else {
try {
const { stdout, stderr } = await runJavaApp('-version', jrePath);
// Check if the command executed successfully
if (stderr.length > 0) {
// If there's output in stderr, it might be the version information
flowElement.log(LogLevel.Info, "Java version output: " + stderr);
retval.push({ tag: "jrePath", valid: true });
} else if (stdout.length > 0) {
// If there's output in stdout, it indicates a successful command execution
flowElement.log(LogLevel.Info, "Java version output: " + stdout);
retval.push({ tag: "jrePath", valid: true });
} else {
// If both stdout and stderr are empty, it indicates an issue
flowElement.log(LogLevel.Error, "No version information received.");
retval.push({ tag: "jrePath", valid: false });
}
} catch (error) {
// This block will catch errors thrown by the execFilePromise or execPromise
flowElement.log(LogLevel.Error, "Custom JRE path is not valid. Please provide a valid JRE path. Error: " + error.message);
retval.push({ tag: "jrePath", valid: false });
}
}
} else if (tag === "replacementChar") {
if (!regexp.test(replacementChar)) {
await flowElement.log(LogLevel.Error, "The replacement character must be one of the following: Letters (a-z, A-Z), Numbers (0-9), Whitespace characters, or any of the symbols that you've put in the 'Allowed characters' field: " + regexpString + ". Or you can leave this field blank, effectively cutting all the characters that are not included in the 'Allowed characters' list or not present in transliteration.json.");
retval.push({ tag: "replacementChar", valid: false });
} else if (replacementChar.length > 1) {
await flowElement.log(LogLevel.Error, "Replacement character must be a single character or may be omitted.");
retval.push({ tag: "replacementChar", valid: false });
} else {
retval.push({ tag: "replacementChar", valid: true });
}
} else if (tag === "renameSpotColor" && !renameSpotColor.endsWith(".jar")) {
await flowElement.log(LogLevel.Error, "Please pick a valid 'normalize_spot_color_names.jar' file.");
retval.push({ tag: "renameSpotColor", valid: false });
} else if (tag === "transliteration" && (transliteration.length > 0 && !transliteration.endsWith(".json"))) {
await flowElement.log(LogLevel.Error, "Please pick a valid .json file.");
retval.push({ tag: "transliteration", valid: false });
} else {
retval.push({ tag: tag, valid: true });
}
}
return retval;
}
async function jobArrived(s, flowElement, job) {
const renameSpotColor = await flowElement.getPropertyStringValue("renameSpotColor");
const transliteration = await flowElement.getPropertyStringValue("transliteration");
const specialChars = await flowElement.getPropertyStringValue("specialChars");
const replacementChar = await flowElement.getPropertyStringValue("replacementChar");
const charsetIn = await flowElement.getPropertyStringValue("charset");
const charset = await charsetIn.split(' ')[0].toLowerCase();
const saveToPrivateData = await flowElement.getPropertyStringValue("saveToPrivateData");
const normalizeNames = await flowElement.getPropertyStringValue("normalizeNames");
const jrePath = await flowElement.getPropertyStringValue("jrePath"); // Get the JRE path
const jobPath = await job.get(AccessLevel.ReadOnly);
// Prepare the arguments for the JAR execution
const args = [
`${jobPath}`,
`${specialChars}`,
`${replacementChar}`,
`${charset}`,
`${transliteration}`,
`${normalizeNames}`
];
try {
const { stdout, stderr } = await runJavaApp('-jar', jrePath, [renameSpotColor, ...args]);
if (stderr.length > 0) {
await job.log(LogLevel.Error, stderr);
await job.Fail();
};
if (stdout.length > 0) {
await job.log(LogLevel.Info, stdout);
if (stdout !== "Renaming of separations not required. Original file remains unchanged.") {
if (saveToPrivateData === "Yes") {
const privateDataTag = await flowElement.getPropertyStringValue("privateDataTag");
await job.setPrivateData(privateDataTag, stdout);
}
}
}
await job.sendToSingle();
} catch (error) {
await job.log(LogLevel.Error, `Error: ${error.message}`);
await job.Fail();
}
}