normalize_spot_color

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();
    }
}

Наверх

Report Page