PdfUtils.java

package edu.jiangxin.apktoolbox.pdf;

import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.AreaBreak;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.properties.HorizontalAlignment;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.text.PDFTextStripper;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Set;

public class PdfUtils {
    private static final Logger LOGGER = LogManager.getLogger(PdfUtils.class.getSimpleName());
    public static boolean isScannedPdf(File file, int threshold) {
        int length = 0;

        try (PDDocument document = Loader.loadPDF(file)) {
            boolean isEncrypted = document.isEncrypted();
            if (isEncrypted) {
                document.setAllSecurityToBeRemoved(true);
            }

            PDFTextStripper stripper = new PDFTextStripper();
            String text = stripper.getText(document).trim();
            length = text.length();
        } catch (IOException e) {
            LOGGER.error("Error reading PDF file: {}", e.getMessage());
            return false;
        }
        LOGGER.info("Processing file: {}, text size: {}", file.getPath(), length);
        return length < threshold;
    }

    public static boolean isEncryptedPdf(File file) {
        boolean isEncrypted;

        try (PDDocument document = Loader.loadPDF(file)) {
            isEncrypted = document.isEncrypted();
        } catch (IOException e) {
            LOGGER.error("Error reading PDF file: {}", e.getMessage());
            return false;
        }
        LOGGER.info("Processing file: {}, is encrypted: {}", file.getPath(), isEncrypted);
        return isEncrypted;
    }

    public static boolean isNonOutlinePdf(File file) {
        boolean hasOutline = false;

        try (PDDocument document = Loader.loadPDF(file)) {
            boolean isEncrypted = document.isEncrypted();
            if (isEncrypted) {
                document.setAllSecurityToBeRemoved(true);
            }

            if (document.getDocumentCatalog() != null && document.getDocumentCatalog().getDocumentOutline() != null) {
                hasOutline = true;
            }
        } catch (IOException e) {
            LOGGER.error("Error reading PDF file: {}", e.getMessage());
            return false;
        }
        LOGGER.info("Processing file: {}, has outline: {}", file.getPath(), hasOutline);
        return !hasOutline;
    }

    public static boolean hasAnnotations(File file) {
        boolean hasAnnotations = false;

        try (PDDocument document = Loader.loadPDF(file)) {
            boolean isEncrypted = document.isEncrypted();
            if (isEncrypted) {
                document.setAllSecurityToBeRemoved(true);
            }
            PDDocumentCatalog catalog = document.getDocumentCatalog();
            if (catalog == null) {
                return false;
            }
            PDPageTree pages = document.getDocumentCatalog().getPages();
            if (pages == null || pages.getCount() == 0) {
                return false;
            }

            for (PDPage page : pages) {
                if (page.getAnnotations() != null && !page.getAnnotations().isEmpty()) {
                    int pageNumber = page.getCOSObject().getInt("PageNumber", 0);
                    String subType = page.getAnnotations().get(0).getSubtype();
                    LOGGER.info("Found annotations on page: {}, subType: {}", pageNumber, subType);
                    if (!subType.equals("Link")) {
                        hasAnnotations = true;
                        break; // No need to check further if we found annotations
                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error("Error reading PDF file: {}", e.getMessage());
            return hasAnnotations;
        }
        LOGGER.info("Processing file: {}, has annotations: {}", file.getPath(), hasAnnotations);
        return hasAnnotations;
    }

    public static void removePassword(File encryptedFile, File targetDir) {
        try (PDDocument document = Loader.loadPDF(encryptedFile)) {
            boolean isEncrypted = document.isEncrypted();
            if (isEncrypted) {
                document.setAllSecurityToBeRemoved(true);
            }
            String targetFilePath = targetDir.getAbsolutePath() + File.separator + encryptedFile.getName();
            document.save(targetFilePath);
            LOGGER.info("Remove password success: {}", targetFilePath);
        } catch (IOException e) {
            LOGGER.error("Error processing PDF file: {}", e.getMessage());
        }
    }

    public static void removePasswordWithIText(File encryptedFile, File targetDir) {
        String targetFilePath = targetDir.getAbsolutePath() + File.separator + encryptedFile.getName();
        PdfReader reader = null;
        PdfDocument pdfDoc = null;
        PdfWriter writer = null;
        try {
            reader = new PdfReader(encryptedFile);
            reader.setUnethicalReading(true);
            writer = new PdfWriter(targetFilePath);
            pdfDoc = new PdfDocument(reader, writer);
        } catch (IOException e) {
            LOGGER.error("Error processing PDF file: {}", e.getMessage());
        } finally {
            IOUtils.closeQuietly(writer);
            IOUtils.closeQuietly(pdfDoc);
            IOUtils.closeQuietly(reader);
        }
    }

    public static int getPageCount(File file) {
        int pageCount = 0;

        try (PDDocument document = Loader.loadPDF(file)) {
            boolean isEncrypted = document.isEncrypted();
            if (isEncrypted) {
                document.setAllSecurityToBeRemoved(true);
            }
            pageCount = document.getNumberOfPages();
        } catch (IOException e) {
            LOGGER.error("Error reading PDF file: {}", e.getMessage());
            return 0;
        }
        LOGGER.info("Processing file: {}, page count: {}", file.getPath(), pageCount);
        return pageCount;
    }

    public static void imagesToPdf(Set<File> images, File targetFile) {
        PdfDocument pdfDoc = null;
        PdfWriter writer = null;
        Document doc = null;
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(targetFile);
            writer = new PdfWriter(fos);
            pdfDoc = new PdfDocument(writer);
            doc = new Document(pdfDoc);

            for (File img : images) {
                BufferedImage bufferedImage = ImageIO.read(img);
                float width = bufferedImage.getWidth();
                float height = bufferedImage.getHeight();

                PageSize pageSize = new PageSize(width, height);
                pdfDoc.addNewPage(pageSize);

                Image image = new Image(ImageDataFactory.create(img.getAbsolutePath()));
                image.setFixedPosition(pdfDoc.getNumberOfPages(), 0, 0, width);

                doc.setMargins(0, 0, 0, 0);
                doc.add(image);
            }
        } catch (IOException e) {
            LOGGER.error("Error processing PDF file: {}", e.getMessage());
        } finally {
            IOUtils.closeQuietly(doc);
            IOUtils.closeQuietly(pdfDoc);
            IOUtils.closeQuietly(writer);
            IOUtils.closeQuietly(fos);
        }
    }
}