All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.princexml.wrapper.Prince Maven / Gradle / Ivy

/*
 * Copyright (C) 2005-2006, 2010, 2012, 2014-2015, 2018, 2021-2022 YesLogic Pty. Ltd.
 * All rights reserved.
 */

package com.princexml.wrapper;

import com.princexml.wrapper.enums.*;
import com.princexml.wrapper.events.PrinceEvents;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import static com.princexml.wrapper.CommandLine.*;

/**
 * Class that provides the default interface to Prince, where each document
 * conversion invokes a new Prince process.
 */
public class Prince extends AbstractPrince {
    // Input options.
    private final List remaps = new ArrayList<>();
    private boolean noLocalFiles;

    // CSS options.
    private String pageSize;
    private String pageMargin;

    // PDF output options.
    private boolean noSystemFonts;
    private int cssDpi;

    // Raster output options.
    private RasterFormat rasterFormat;
    private int rasterJpegQuality = -1;
    private int rasterPage;
    private int rasterDpi;
    private int rasterThreads = -1;
    private RasterBackground rasterBackground;

    // Additional options.
    private final List options = new ArrayList<>();

    /**
     * Constructor for {@code Prince}.
     * @param princePath The path of the Prince executable. For example, this may be
     *                   C:\Program Files\Prince\engine\bin\prince.exe
     *                   on Windows or /usr/bin/prince on Linux.
     */
    public Prince(String princePath) {
        super(princePath);
    }

    /**
     * Constructor for {@code Prince}.
     * @param princePath The path of the Prince executable. For example, this may be
     *                   C:\Program Files\Prince\engine\bin\prince.exe
     *                   on Windows or /usr/bin/prince on Linux.
     * @param events An implementation of {@link com.princexml.wrapper.events.PrinceEvents}
     *               that will receive messages returned from Prince.
     */
    public Prince(String princePath, PrinceEvents events) {
        super(princePath, events);
    }

    /**
     * Convert an XML or HTML file to a PDF file. The name of the output PDF
     * file will be the same as the name of the input file, but with the
     * {@code .pdf} extension.
     * @param inputPath The filename of the input XML or HTML document.
     * @return true if a PDF file was generated successfully.
     * @throws IOException If an I/O error occurs.
     */
    public boolean convert(String inputPath) throws IOException {
        return convertInternal(Collections.singletonList(inputPath), null);
    }

    /**
     * Convert an XML or HTML file to a PDF file.
     * @param inputPath The filename of the input XML or HTML document.
     * @param outputPath The filename of the output PDF file.
     * @return true if a PDF file was generated successfully.
     * @throws IOException If an I/O error occurs.
     */
    public boolean convert(String inputPath, String outputPath) throws IOException {
        return convertInternal(Collections.singletonList(inputPath), outputPath);
    }

    /**
     * Convert multiple XML or HTML files to a PDF file.
     * @param inputPaths The filenames of the input XML or HTML documents.
     * @param outputPath The filename of the output PDF file.
     * @return true if a PDF file was generated successfully.
     * @throws IOException If an I/O error occurs.
     */
    public boolean convert(List inputPaths, String outputPath) throws IOException {
        return convertInternal(inputPaths, outputPath);
    }

    private boolean convertInternal(List inputPaths, String outputPath) throws IOException {
        List cmdLine = getJobCommandLine("normal");
        cmdLine.addAll(inputPaths);
        if (outputPath != null) {
            cmdLine.add(toCommand("output", outputPath));
        }

        Process process = Util.invokeProcess(cmdLine);

        return readMessagesFromStderr(process);
    }

    /** {@inheritDoc} */
    @Override
    public boolean convert(String inputPath, OutputStream output) throws IOException {
        return convert(Collections.singletonList(inputPath), output);
    }

    /** {@inheritDoc} */
    @Override
    public boolean convert(List inputPaths, OutputStream output) throws IOException {
        List cmdLine = getJobCommandLine("buffered");
        cmdLine.addAll(inputPaths);
        cmdLine.add(toCommand("output", "-"));

        Process process = Util.invokeProcess(cmdLine);

        try (InputStream fromPrince = process.getInputStream()) {
            Util.copyInputToOutput(fromPrince, output);
        }

        return readMessagesFromStderr(process);
    }

    /** {@inheritDoc} */
    @Override
    public boolean convert(InputStream input, OutputStream output) throws IOException {
        if (inputType == null || inputType == InputType.AUTO) {
            throw new RuntimeException("inputType has to be set to XML or HTML");
        }

        List cmdLine = getJobCommandLine("buffered");
        cmdLine.add("-");

        Process process = Util.invokeProcess(cmdLine);

        try (OutputStream toPrince = process.getOutputStream()) {
            Util.copyInputToOutput(input, toPrince);
        }

        try (InputStream fromPrince = process.getInputStream()) {
            Util.copyInputToOutput(fromPrince, output);
        }

        return readMessagesFromStderr(process);
    }

    /**
     * Convert an XML or HTML string to a PDF file.
     * @param input The XML or HTML document in the form of a String.
     * @param outputPath The filename of the output PDF file.
     * @return true if a PDF file was generated successfully.
     * @throws IOException If an I/O error occurs.
     */
    public boolean convertString(String input, String outputPath) throws IOException {
        if (inputType == null || inputType == InputType.AUTO) {
            throw new RuntimeException("inputType has to be set to XML or HTML");
        }

        List cmdLine = getJobCommandLine("buffered");
        cmdLine.add(toCommand("output", outputPath));
        cmdLine.add("-");

        Process process = Util.invokeProcess(cmdLine);

        try (OutputStream toPrince = process.getOutputStream()) {
            toPrince.write(input.getBytes(StandardCharsets.UTF_8));
        }

        return readMessagesFromStderr(process);
    }

    /** {@inheritDoc} */
    @Override
    public boolean convertString(String input, OutputStream output) throws IOException {
        try (InputStream in = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) {
            return convert(in, output);
        }
    }

    /**
     * Rasterize an XML or HTML file.
     * @param inputPath The filename of the input XML or HTML document.
     * @param outputPath A template string from which the raster files will be named
     *                   (e.g. "page_%02d.png" will cause Prince to generate
     *                   page_01.png, page_02.png, ..., page_10.png etc.).
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterize(String inputPath, String outputPath) throws IOException {
        return rasterizeInternal(Collections.singletonList(inputPath), outputPath);
    }

    /**
     * Rasterize multiple XML or HTML files.
     * @param inputPaths The filenames of the input XML or HTML documents.
     * @param outputPath A template string from which the raster files will be named
     *                   (e.g. "page_%02d.png" will cause Prince to generate
     *                   page_01.png, page_02.png, ..., page_10.png etc.).
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterize(List inputPaths, String outputPath) throws IOException {
        return rasterizeInternal(inputPaths, outputPath);
    }

    private boolean rasterizeInternal(List inputPaths, String outputPath) throws IOException {
        List cmdLine = getJobCommandLine("normal");
        cmdLine.addAll(inputPaths);
        cmdLine.add(toCommand("raster-output", outputPath));

        Process process = Util.invokeProcess(cmdLine);

        return readMessagesFromStderr(process);
    }

    /**
     * Rasterize an XML or HTML file. This method is useful for servlets as it
     * allows Prince to write the raster output directly to the {@code OutputStream}
     * of the servlet response.
     * @param inputPath The filename of the input XML or HTML document.
     * @param output The OutputStream to which Prince will write the raster output.
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterize(String inputPath, OutputStream output) throws IOException {
        return rasterize(Collections.singletonList(inputPath), output);
    }

    /**
     * Rasterize multiple XML or HTML files. This method is useful for servlets as it
     * allows Prince to write the raster output directly to the {@code OutputStream}
     * of the servlet response.
     * @param inputPaths The filenames of the input XML or HTML documents.
     * @param output The OutputStream to which Prince will write the raster output.
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterize(List inputPaths, OutputStream output) throws IOException {
        if (rasterPage < 1) {
            throw new RuntimeException("rasterPage has to be set to a value > 0");
        }
        if (rasterFormat == null || rasterFormat == RasterFormat.AUTO) {
            throw new RuntimeException("rasterFormat has to be set to JPEG or PNG");
        }

        List cmdLine = getJobCommandLine("buffered");
        cmdLine.addAll(inputPaths);
        cmdLine.add(toCommand("raster-output", "-"));

        Process process = Util.invokeProcess(cmdLine);

        try (InputStream fromPrince = process.getInputStream()) {
            Util.copyInputToOutput(fromPrince, output);
        }

        return readMessagesFromStderr(process);
    }

    /**
     * Rasterize an XML or HTML stream. This method is useful for servlets as it
     * allows Prince to write the raster output directly to the {@code OutputStream}
     * of the servlet response.
     * @param input The InputStream from which Prince will read the XML or HTML
     *              document.
     * @param output The OutputStream to which Prince will write the raster output.
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterize(InputStream input, OutputStream output) throws IOException {
        if (inputType == null || inputType == InputType.AUTO) {
            throw new RuntimeException("inputType has to be set to XML or HTML");
        }
        if (rasterPage < 1) {
            throw new RuntimeException("rasterPage has to be set to a value > 0");
        }
        if (rasterFormat == null || rasterFormat == RasterFormat.AUTO) {
            throw new RuntimeException("rasterFormat has to be set to JPEG or PNG");
        }

        List cmdLine = getJobCommandLine("buffered");
        cmdLine.add(toCommand("raster-output", "-"));
        cmdLine.add("-");

        Process process = Util.invokeProcess(cmdLine);

        try (OutputStream toPrince = process.getOutputStream()) {
            Util.copyInputToOutput(input, toPrince);
        }

        try (InputStream fromPrince = process.getInputStream()) {
            Util.copyInputToOutput(fromPrince, output);
        }

        return readMessagesFromStderr(process);
    }

    /**
     * Rasterize an XML or HTML string.
     * @param input The XML or HTML document in the form of a String.
     * @param outputPath A template string from which the raster files will be named
     *                   (e.g. "page_%02d.png" will cause Prince to generate
     *                   page_01.png, page_02.png, ..., page_10.png etc.).
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterizeString(String input, String outputPath) throws IOException {
        if (inputType == null || inputType == InputType.AUTO) {
            throw new RuntimeException("inputType has to be set to XML or HTML");
        }

        List cmdLine = getJobCommandLine("buffered");
        cmdLine.add(toCommand("raster-output", outputPath));
        cmdLine.add("-");

        Process process = Util.invokeProcess(cmdLine);

        try (OutputStream toPrince = process.getOutputStream()) {
            toPrince.write(input.getBytes(StandardCharsets.UTF_8));
        }

        return readMessagesFromStderr(process);
    }

    /**
     * Rasterize an XML or HTML string. This method is useful for servlets as it
     * allows Prince to write the raster output directly to the {@code OutputStream}
     * of the servlet response.
     * @param input The XML or HTML document in the form of a String.
     * @param output The OutputStream to which Prince will write the raster output.
     * @return true if the input was successfully rasterized.
     * @throws IOException If an I/O error occurs.
     */
    public boolean rasterizeString(String input, OutputStream output) throws IOException {
        try (InputStream in = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) {
            return rasterize(in, output);
        }
    }

    private List getJobCommandLine(String logType) {
        List cmdLine = getBaseCommandLine();

        cmdLine.add(toCommand("structured-log", logType));

        if (inputType != null) { cmdLine.add(toCommand("input", inputType)); }
        if (baseUrl != null) { cmdLine.add(toCommand("baseurl", baseUrl)); }
        if (!remaps.isEmpty()) { cmdLine.addAll(toCommands("remap", remaps)); }
        if (iframes) { cmdLine.add(toCommand("iframes")); }
        if (xInclude) { cmdLine.add(toCommand("xinclude")); }
        if (xmlExternalEntities) { cmdLine.add(toCommand("xml-external-entities")); }
        if (noLocalFiles) { cmdLine.add(toCommand("no-local-files")); }

        if (javaScript) { cmdLine.add(toCommand("javascript")); }
        if (!scripts.isEmpty()) { cmdLine.addAll(toCommands("script", scripts)); }
        if (maxPasses > 0) { cmdLine.add(toCommand("max-passes", maxPasses)); }

        if (!styleSheets.isEmpty()) { cmdLine.addAll(toCommands("style", styleSheets)); }
        if (media != null) { cmdLine.add(toCommand("media", media)); }
        if (pageSize != null) { cmdLine.add(toCommand("page-size", pageSize)); }
        if (pageMargin != null) { cmdLine.add(toCommand("page-margin", pageMargin)); }
        if (noAuthorStyle) { cmdLine.add(toCommand("no-author-style")); }
        if (noDefaultStyle) { cmdLine.add(toCommand("no-default-style")); }

        if (pdfId != null) { cmdLine.add(toCommand("pdf-id", pdfId)); }
        if (pdfLang != null) { cmdLine.add(toCommand("pdf-lang", pdfLang)); }
        if (pdfProfile != null) { cmdLine.add(toCommand("pdf-profile", pdfProfile)); }
        if (pdfOutputIntent != null) { cmdLine.add(toCommand("pdf-output-intent", pdfOutputIntent)); }
        if (pdfScript != null) { cmdLine.add(toCommand("pdf-script", pdfScript)); }
        pdfEventScripts.forEach((k, v) -> cmdLine.add(toCommand("pdf-event-script", k + ":" + v)));
        if (!fileAttachments.isEmpty()) { cmdLine.addAll(toCommands("attach",
                fileAttachments.stream().map(a -> a.url).collect(Collectors.toList()))); }
        if (noArtificialFonts) { cmdLine.add(toCommand("no-artificial-fonts")); }
        if (noEmbedFonts) { cmdLine.add(toCommand("no-embed-fonts")); }
        if (noSubsetFonts) { cmdLine.add(toCommand("no-subset-fonts")); }
        if (noSystemFonts) { cmdLine.add(toCommand("no-system-fonts")); }
        if (forceIdentityEncoding) { cmdLine.add(toCommand("force-identity-encoding")); }
        if (noCompress) { cmdLine.add(toCommand("no-compress")); }
        if (noObjectStreams) { cmdLine.add(toCommand("no-object-streams")); }
        if (convertColors) { cmdLine.add(toCommand("convert-colors")); }
        if (fallbackCmykProfile != null) { cmdLine.add(toCommand("fallback-cmyk-profile", fallbackCmykProfile)); }
        if (taggedPdf) { cmdLine.add(toCommand("tagged-pdf")); }
        if (pdfForms) { cmdLine.add(toCommand("pdf-forms")); }
        if (cssDpi > 0) {cmdLine.add(toCommand("css-dpi", cssDpi)); }

        if (pdfTitle != null) { cmdLine.add(toCommand("pdf-title", pdfTitle)); }
        if (pdfSubject != null) { cmdLine.add(toCommand("pdf-subject", pdfSubject)); }
        if (pdfAuthor != null) { cmdLine.add(toCommand("pdf-author", pdfAuthor)); }
        if (pdfKeywords != null) { cmdLine.add(toCommand("pdf-keywords", pdfKeywords)); }
        if (pdfCreator != null) { cmdLine.add(toCommand("pdf-creator", pdfCreator)); }
        if (xmp != null) { cmdLine.add(toCommand("pdf-xmp", xmp)); }

        if (encrypt) { cmdLine.add(toCommand("encrypt")); }
        if (keyBits != null) { cmdLine.add(toCommand("key-bits", keyBits)); }
        if (userPassword != null) { cmdLine.add(toCommand("user-password", userPassword)); }
        if (ownerPassword != null) { cmdLine.add(toCommand("owner-password", ownerPassword)); }
        if (disallowPrint) { cmdLine.add(toCommand("disallow-print")); }
        if (disallowCopy) { cmdLine.add(toCommand("disallow-copy")); }
        if (allowCopyForAccessibility) { cmdLine.add(toCommand("allow-copy-for-accessibility")); }
        if (disallowAnnotate) { cmdLine.add(toCommand("disallow-annotate")); }
        if (disallowModify) { cmdLine.add(toCommand("disallow-modify")); }
        if (allowAssembly) { cmdLine.add(toCommand("allow-assembly")); }

        if (rasterFormat != null) { cmdLine.add(toCommand("raster-format", rasterFormat)); }
        if (rasterJpegQuality > -1) { cmdLine.add(toCommand("raster-jpeg-quality", rasterJpegQuality)); }
        if (rasterPage > 0) { cmdLine.add(toCommand("raster-pages", rasterPage)); }
        if (rasterDpi > 0) { cmdLine.add(toCommand("raster-dpi", rasterDpi)); }
        if (rasterThreads > -1) { cmdLine.add(toCommand("raster-threads", rasterThreads)); }
        if (rasterBackground != null) { cmdLine.add(toCommand("raster-background", rasterBackground)); }

        options.forEach(o -> cmdLine.add(o.snd == null ? toCommand(o.fst) : toCommand(o.fst, o.snd)));

        return cmdLine;
    }

    private boolean readMessagesFromStderr(Process process) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
            return readMessages(reader);
        }
    }

    //region Input options.
    /**
     * Rather than retrieving documents beginning with {@code url}, get them
     * from the local directory {@code dir}. This method can be called more than
     * once to add multiple remappings.
     * @param url The URL to map to a directory.
     * @param directory The directory that a URL is mapped to.
     */
    public void addRemap(String url, String directory) {
        this.remaps.add(url + "=" + directory);
    }

    /**
     * Clear all of the remappings accumulated by calling {@link #addRemap(String, String)}.
     */
    public void clearRemaps() {
        this.remaps.clear();
    }

    /**
     * Disable access to local files. Default value is {@code false}.
     * @param noLocalFiles true to disable local files.
     */
    public void setNoLocalFiles(boolean noLocalFiles) {
        this.noLocalFiles = noLocalFiles;
    }
    //endregion

    //region CSS options.
    /**
     * Specify the page size.
     * @param pageSize The page size to use (e.g. "A4").
     */
    public void setPageSize(String pageSize) {
        this.pageSize = pageSize;
    }

    /**
     * Specify the page margin.
     * @param pageMargin The page margin to use (e.g. "20mm").
     */
    public void setPageMargin(String pageMargin) {
        this.pageMargin = pageMargin;
    }
    //endregion

    //region PDF output options.
    /**
     * Disable the use of system fonts. Only fonts defined with {@code font-face}
     * rules in CSS will be available. Default value is {@code false}.
     * @param noSystemFonts true to disable system fonts.
     */
    public void setNoSystemFonts(boolean noSystemFonts) {
        this.noSystemFonts = noSystemFonts;
    }

    /**
     * Changes the DPI of the "px" units in CSS. Default value is 96 dpi.
     * @param cssDpi The DPI of the "px" units. Value must be greater than 0.
     */
    public void setCssDpi(int cssDpi) {
        if (cssDpi < 1) {
            throw new IllegalArgumentException("invalid cssDpi value (must be > 0)");
        }
        this.cssDpi = cssDpi;
    }
    //endregion

    //region Raster output options.
    /**
     * Specify the format for the raster output. Default value is
     * {@link com.princexml.wrapper.enums.RasterFormat#AUTO}.
     * @param rasterFormat The format for the raster output.
     */
    public void setRasterFormat(RasterFormat rasterFormat) {
        this.rasterFormat = rasterFormat;
    }

    /**
     * Specify the level of JPEG compression when generating raster output in JPEG format.
     * Default value is 92 percent.
     * @param rasterJpegQuality The level of JPEG compression. Valid range is between 0
     *                          and 100 inclusive.
     */
    public void setRasterJpegQuality(int rasterJpegQuality) {
        if (rasterJpegQuality < 0 || rasterJpegQuality > 100) {
            throw new IllegalArgumentException("invalid rasterJpegQuality value (must be [0, 100])");
        }
        this.rasterJpegQuality = rasterJpegQuality;
    }

    /**
     * Set the page number to be rasterized. Defaults to rasterizing all pages.
     * @param rasterPage The page number to be rasterized. Value must be greater than 0.
     */
    public void setRasterPage(int rasterPage) {
        if (rasterPage < 1) {
            throw new IllegalArgumentException("invalid rasterPage value (must be > 0)");
        }
        this.rasterPage = rasterPage;
    }

    /**
     * Specify the resolution of raster output. Default value is 96 dpi.
     * @param rasterDpi The raster output resolution. Value must be greater than 0.
     */
    public void setRasterDpi(int rasterDpi) {
        if (rasterDpi < 1) {
            throw new IllegalArgumentException("invalid rasterDpi value (must be > 0)");
        }
        this.rasterDpi = rasterDpi;
    }

    /**
     * Set the number of threads to use for multi-threaded rasterization. Default
     * value is the number of cores and hyperthreads the system provides.
     * @param rasterThreads The number of threads to use.
     */
    public void setRasterThreads(int rasterThreads) {
        this.rasterThreads = rasterThreads;
    }

    /**
     * Set the background. Can be used when rasterizing to an image format that
     * supports transparency. Default value is {@link com.princexml.wrapper.enums.RasterBackground#WHITE}.
     * @param rasterBackground The raster background.
     */
    public void setRasterBackground(RasterBackground rasterBackground) {
        this.rasterBackground = rasterBackground;
    }
    //endregion

    //region Additional options.
    /**
     * Specify additional Prince command-line options.
     * @param key The command-line option.
     */
    public void addOption(String key) {
        this.options.add(new Pair(key));
    }

    /**
     * Specify additional Prince command-line options.
     * @param key The command-line option key.
     * @param value The command-line option value.
     */
    public void addOption(String key, String value) {
        this.options.add(new Pair(key, value));
    }

    /**
     * Clear the additional command-line options accumulated by calling
     * {@link #addOption(String)} or {@link #addOption(String, String)}.
     */
    public void clearOptions() {
        this.options.clear();
    }
    //endregion
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy