/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
 
 *
 * ---------------
 * ConvertPagesToImages.java
 * ---------------
 */

package org.jpedal.examples.images;

import com.idrsolutions.image.tiff.TiffEncoder;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;

import org.jpedal.PdfDecoderServer;
import org.jpedal.color.ColorSpaces;
import org.jpedal.constants.PageInfo;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.FontMappings;
import org.jpedal.objects.PdfFileInformation;

/**
 <h2>Image Extraction from PDF files</h2>
 <p>
 * This class provides a simple Java API to extract pages as images from a PDF file and also
 * a static convenience method if you just want to dump all the pages as images from a PDF file
 * or directory containing PDF files
 * isBackgroundTransparent <b>MUST</b> be false for generating JPEG images
 <p>
 <p>
 * Note: It is recommended to resort to ConverPagesToHiResImages as the first choice for generating better quality images when converting with non-default settings.
 <p>
 <h3>Example 1 - access API methods</h3>
 <pre><code>ConvertPagesToImages extract=new ConvertPagesToImages("C:/pdfs/mypdf.pdf");
 * //extract.setPassword("password");
 *
 * //reduce size of image (also reduces memory usage
 * //extract.setPageScaling(0.25f); // default values is 1.33f to give same size as Adobe at 100%
 *
 * if (extract.openPDFFile()) {
 *     int pageCount=extract.getPageCount();
 *
 *        BufferedImage image=extract.getPageAsImage(page, isBackgroundTransparent);
 *     }
 * }
 *
 * extract.closePDFfile();</code></pre>
 <p>
 <h3>Example 2 - convenience static method</h3>
 * Extract all pages as images<p>
 <p>
 <pre><code>ConvertPagesToImages.writeAllPagesAsImagesToDir("pdfs", "images" , "png", 1.33f);</code></pre>
 *
 <h3>Example 3 - Access directly from the Jar</h3>
 * ConvertPagesToImages can run from jar directly using the command and will extract all files from a PDF file or directory
 * to a defined output directory:<p>
 *
 <code>java -cp libraries_needed org/jpedal/examples/images/ConvertPagesToImages inputValues</code><p>
 *
 * Where inputValues is 4 values:
 <ul>
 <li>First value:  The PDF filename (including the path if needed) or a directory containing PDF files. If it contains spaces it must be enclosed by double quotes (ie "C:/Path with spaces/").</li>
 <li>Second value: The location to write out images extracted from the PDF file or files. If it contains spaces it must be enclosed by double quotes (ie "C:/Path with spaces/").</li>
 <li>Third value:  This indicates the required output image type (default is png if nothing specified). Options are tiff, png, jpg.</li>
 <li>Fourth value: Scaling of page 1.33f gives same size as page appears in Acrobat at 100%</li>
 </ul>
 *
 * There is another example (org.jpedal.examples.images.ConvertPagesToHiResImages) for producing higher res images of pages (but likely to be slower).
 <p><a href="http://www.idrsolutions.com/how-to-convert-pdf-files-to-image">Click here for a list of code examples to convert images</a></p>
 */
public class ConvertPagesToImages extends BaseImageExtraction {

    /**
     * show if image transparent
     */
    boolean isTransparent;

    /**
     * use 96 dpi as default so pages correct size (72 will be smaller)
     */
    private float pageScaling = 1.33f;

    /**
     * holding all creators that produce OCR pdf's
     */
    private final String[] ocr = {"TeleForm"};

    /**
     * used as part of test to limit pages to first 10 - please do not use
     */
    public static int maxPageCount = -1;

    //only used if between 0 and 1
    private float JPEGcompression = -1f;

    /**
     * convenience static method to convert PDF file or directory of files
     */
    public static void writeAllPagesAsImagesToDir(final String inputDir, final String outDir, final String format, final float pageScalingthrows PdfException {

        final ConvertPagesToImages convert = new ConvertPagesToImages(inputDir);

        convert.setup(format, outDir, pageScaling);

        convert.processFiles(inputDir);

        convert.closePDFfile();
    }

    /**
     * alter page scaling (default is 1.33f which gives same size as Acrobat at 100)
     *
     @param pageScaling
     */
    public void setPageScaling(final float pageScaling) {
        this.pageScaling = pageScaling;

        decode_pdf.setExtractionMode(0, pageScaling);
    }

    void setup(final String format, String outDir, final float pageScalingthrows PdfException, RuntimeException {

        //check output dir has separator
        if (!outDir.endsWith(separator)) {
            outDir += separator;
        }

        this.imageType = format;
        this.output_dir = outDir;
        this.pageScaling = pageScaling;

        //check output dir has separator
        if (!user_dir.endsWith(separator)) {
            user_dir += separator;
        }
    }

    @Override
    void processFiles(String inputDirthrows PdfException, RuntimeException {

        //check file exists
        final File pdf_file = new File(inputDir);
        //if file exists, open and get number of pages
        if (!pdf_file.exists()) {
            throw new RuntimeException("File " + pdf_file + " not found");
        }

        //check output dir has separator
        if (!user_dir.endsWith(separator)) {
            user_dir += separator;
        }
        /*
         * if file name ends pdf, do the file otherwise
         * do every pdf file in the directory. We already know file or
         * directory exists so no need to check that, but we do need to
         * check its a directory
         */
        if (inputDir.toLowerCase().endsWith(".pdf")) {

            try {
                decodeFile(inputDir);
            catch (final Exception e) {
                System.err.println("ConvertPagesToImages " + e + " in pdf code in " + inputDir);
            }
        else {

            String[] files = null;
            final File inputFiles;
            //make sure name ends with a deliminator for correct path later
            if (!inputDir.endsWith(separator)) {
                inputDir += separator;
            }
            try {
                inputFiles = new File(inputDir);
                if (!inputFiles.isDirectory()) {
                    System.err.println(inputDir + " is not a directory. Exiting program");
                else {
                    files = inputFiles.list();
                }
            catch (final Exception ee) {
                throw new PdfException("Exception trying to access file " + ee.getMessage());

            }
            if (files != null) {
                //now work through all pdf files
                for (final String file : files) {
                    if (file.toLowerCase().endsWith(".pdf")) {

                        try {
                            decodeFile(inputDir + file);
                        catch (final Exception e1) {
                            System.err.println("ConvertPagesToImages " + e1 + " in pdf code in " + inputDir);
                        }
                    }
                }
            }
        }
    }

    /**
     * Sets up an ConvertPagesToImages instance to open a PDF File
     *
     @param fileName full path to a single PDF file
     */
    public ConvertPagesToImages(final String fileName) {
        super(fileName);

        init();
    }

    /**
     * Sets up an ConvertPagesToImages instance to open  a PDF file contained as a BLOB within a byte[] stream
     * (do not pad with additional empty bytes)
     *
     @param byteArray
     */
    public ConvertPagesToImages(final byte[] byteArray) {
        super(byteArray);

        init();

    }

    public ConvertPagesToImages(final InputStream inputStream) {
        super(inputStream);

        init();
    }

    /**
     * routine to decode a file
     */
    @Override
    void decodeFile(final String file_namethrows PdfException {


        String name = "demo"//set a default just in case

        int pointer = file_name.lastIndexOf(separator);

        if (pointer == -1) {
            pointer = file_name.lastIndexOf('/');
        }

        if (pointer != -1) {
            name = file_name.substring(pointer + 1, file_name.length() 4);
        else if (file_name.toLowerCase().endsWith(".pdf")) {
            name = file_name.substring(0, file_name.length() 4);
        }

        //fix for odd files on Linux created when you view pages
        if (name.startsWith(".")) {
            return;
        }

        //create output dir for images
        if (output_dir == null) {
            output_dir = user_dir + "thumbnails" + separator;
        }

        //true as we are rendering page
        decode_pdf.setExtractionMode(0, pageScaling);
        //don't bother to extract text and images

        fileName = file_name;

        if (openPDFFile()) {

            //create a directory if it doesn't exist
            final File output_path = new File(output_dir);
            if (!output_path.exists()) {
                output_path.mkdirs();
            }

                         /*
             * allow output to multiple images with different values on each
             *
             * Note we REMOVE shapes as it is a new feature and we do not want to break existing functions
             */
            final String separation = System.getProperty("org.jpedal.separation");
            if (separation != null) {

                Object[] sepValues = {7"", Boolean.FALSE}//default of normal
                if (separation.equals("all")) {
                    sepValues = new Object[]{PdfDecoderServer.RENDERIMAGES, "image_and_shapes", Boolean.FALSE,
                            PdfDecoderServer.RENDERIMAGES + PdfDecoderServer.REMOVE_RENDERSHAPES, "image_without_shapes", Boolean.FALSE,
                            PdfDecoderServer.RENDERTEXT, "text_and_shapes", Boolean.TRUE,
                            7"all", Boolean.FALSE,
                            PdfDecoderServer.RENDERTEXT + PdfDecoderServer.REMOVE_RENDERSHAPES, "text_without_shapes", Boolean.TRUE
                    };
                }

                final int sepCount = sepValues.length;
                for (int seps = 0; seps < sepCount; seps += 3) {

                    decode_pdf.setRenderMode((IntegersepValues[seps]);
                    extractPagesAsImages(file_name, output_dir, name + '_' + sepValues[seps + 1](BooleansepValues[seps + 2])//boolean makes last transparent so we can see white text

                }

            else {
                //just get the page
                extractPagesAsImages(file_name, output_dir, name, isTransparent);
            }
        }

        closePDFfile();

    }

    private void extractPagesAsImages(final String file_name, final String output_dir, final String name, final boolean isTransparent) {

        //create a directory if it doesn't exist
        final File output_path = new File(output_dir);
        if (!output_path.exists()) {
            output_path.mkdirs();
        }

        final String multiPageFlag = System.getProperty("org.jpedal.multipage_tiff");
        final boolean isSingleOutputFile = multiPageFlag != null && multiPageFlag.equalsIgnoreCase("true");

        //allow user to specify value
        final String rawJPEGComp = System.getProperty("org.jpedal.compression_jpeg");
        if (rawJPEGComp != null) {
            try {
                JPEGcompression = Float.parseFloat(rawJPEGComp);
            catch (final Exception e) {
                e.printStackTrace();
            }
            if (JPEGcompression < || JPEGcompression > 1) {
                throw new RuntimeException("Invalid value for JPEG compression - must be between 0 and 1");
            }

        }

        final String tiffFlag = System.getProperty("org.jpedal.compress_tiff");
        final boolean compressTiffs = tiffFlag != null && tiffFlag.equalsIgnoreCase("true");

        //page range
        final int start = 1;
        int end = getPageCount();

        //limit to 1st ten pages in testing
        if (end > 10 && maxPageCount > 0) {
            end = maxPageCount;
        }

        try {

            for (int page = start; page < end + 1; page++) {
                getPage(output_dir, name, isTransparent, isSingleOutputFile, compressTiffs, start, end, page);
            }


        catch (final Exception e) {

            throw new RuntimeException("Exception " + e.getMessage() " with thumbnails on File=" + file_name);
        }
    }

    private void getPage(final String output_dir, final String name, final boolean isTransparent,
                         final boolean isSingleOutputFile,
                         final boolean compressTiffs, final int start, final int end, final int page)
            throws PdfException, IOException {
        //read pages

            // create a name with zeros for if more than 9 pages appears in correct order
            String pageAsString = String.valueOf(page);
            final String maxPageSize = String.valueOf(end);
            final int padding = maxPageSize.length() - pageAsString.length();
            for (int ii = 0; ii < padding; ii++) {
                pageAsString = '0' + pageAsString;
            }

            final String image_name;
            if (isSingleOutputFile) {
                image_name = name;
            else {
                image_name = name + "_page_" + pageAsString;
            }
            
            /*
             * get PRODUCER and if OCR disable text printing
             */
            final PdfFileInformation currentFileInformation = decode_pdf.getFileInformationData();

            final String[] values = currentFileInformation.getFieldValues();
            final String[] fields = PdfFileInformation.getFieldNames();

            for (int i = 0; i < fields.length; i++) {

                if (fields[i].equals("Creator")) {

                    for (final String anOcr : ocr) {

                        if (values[i].equals(anOcr)) {

                            decode_pdf.setRenderMode(PdfDecoderServer.RENDERIMAGES);

                        }
                    }
                }
            }
            
            /*
             * get the current page as a BufferedImage
             */
            BufferedImage image_to_save = getPageAsImage(page, isTransparent);

            if (isTransparent) {

                
                //java adds odd tint if you save this as JPEG which does not have transparency
                // so put as RGB on white background
                // (or save as PNG or TIFF which has transparency)
                // or just call decode_pdf.getPageAsImage(page)
                if (image_to_save != null && imageType.toLowerCase().startsWith("jp")) {

                    final BufferedImage rawVersion = image_to_save;

                    final int w = rawVersion.getWidth();
                    final int h = rawVersion.getHeight();
                    //blank canvas
                    image_to_save = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);

                    //
                    final Graphics2D g2 = image_to_save.createGraphics();
                    //white background
                    g2.setPaint(Color.WHITE);
                    g2.fillRect(00, w, h);
                    //paint on image
                    g2.drawImage(rawVersion, 00null);
                }
            }

            
            //if just gray we can reduce memory usage by converting image to Grayscale
            
            /*
             * see what Colorspaces used and reduce image if appropriate
             * (only does Gray at present)
             *
             * null if JPedal unsure
             */
            final Iterator colorspacesUsed = decode_pdf.getPageInfo(PageInfo.COLORSPACES);

            int nextID;
            boolean isGrayOnly = colorspacesUsed != null//assume true and disprove
            while (colorspacesUsed != null && colorspacesUsed.hasNext()) {
                nextID = (Integer) (colorspacesUsed.next());

                if (nextID != ColorSpaces.DeviceGray && nextID != ColorSpaces.CalGray) {
                    isGrayOnly = false;
                }
            }

            //draw onto GRAY image to reduce colour depth
            //(converts ARGB to gray)
            if (isGrayOnly) {
                final BufferedImage image_to_save2 = new BufferedImage(image_to_save.getWidth(), image_to_save.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
                image_to_save2.getGraphics().drawImage(image_to_save, 00null);
                image_to_save = image_to_save2;
            }

            if (image_to_save != null) {

                /*allow user to specify maximum dimension for thumbnail*/
                final String maxDimensionAsString = System.getProperty("maxDimension");
                int maxDimension = -1;

                if (maxDimensionAsString != null) {
                    maxDimension = Integer.parseInt(maxDimensionAsString);
                }

                if (maxDimension != -1) {
                    int newWidth = image_to_save.getWidth();
                    int newHeight = image_to_save.getHeight();

                    final Image scaledImage;
                    if (maxDimension != -&& (newWidth > maxDimension || newHeight > maxDimension)) {
                        if (newWidth > newHeight) {
                            newWidth = maxDimension;
                            scaledImage = image_to_save.getScaledInstance(newWidth, -1, BufferedImage.SCALE_SMOOTH);
                        else {
                            newHeight = maxDimension;
                            scaledImage = image_to_save.getScaledInstance(-1, newHeight, BufferedImage.SCALE_SMOOTH);
                        }
                    else {
                        scaledImage = image_to_save.getScaledInstance(newWidth, -1, BufferedImage.SCALE_SMOOTH);
                    }

                    if (imageType.toLowerCase().startsWith("jp")) {
                        image_to_save = new BufferedImage(scaledImage.getWidth(null), scaledImage.getHeight(null), BufferedImage.TYPE_INT_RGB);
                    else {
                        image_to_save = new BufferedImage(scaledImage.getWidth(null), scaledImage.getHeight(null), BufferedImage.TYPE_INT_ARGB);
                    }

                    final Graphics2D g2 = image_to_save.createGraphics();

                    g2.drawImage(scaledImage, 00null);
                }

                final String imageFormat = System.getProperty("org.jpedal.imageType");
                if (imageFormat != null) {
                    if (isNumber(imageFormat)) {
                        final int iFormat = Integer.parseInt(imageFormat);
                        if (iFormat > -&& iFormat < 14) {
                            final BufferedImage tempImage = new BufferedImage(image_to_save.getWidth(), image_to_save.getHeight(), iFormat);
                            final Graphics2D g = tempImage.createGraphics();
                            g.drawImage(image_to_save, null, null);

                            image_to_save = tempImage;
                        else {
                            System.err.println("Image Type is not valid. Value should be a digit between 0 - 13 based on the BufferedImage TYPE variables.");
                        }
                    else {
                        System.err.println("Image Type provided is not an Integer. Value should be a digit between 0 - 13 based on the BufferedImage TYPE variables.");
                    }
                }

                if (imageType.toLowerCase().startsWith("tif")) {

                    final String outputFileName;
                    final boolean isFirstPage = page == start;

                    final TiffEncoder tiffEncoder = new TiffEncoder();
                    tiffEncoder.setCompressed(compressTiffs);

                    if (isSingleOutputFile) {
                        outputFileName = output_dir + image_name + ".tif";
                        final File file = new File(outputFileName);
                        if (isFirstPage && file.exists()) {
                            file.delete();
                            file.createNewFile();
                        }
                        tiffEncoder.append(image_to_save, outputFileName);

                    else {
                        outputFileName = output_dir + pageAsString + image_name + ".tif";
                        final File file = new File(outputFileName);
                        final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                        tiffEncoder.write(image_to_save, bos);
                        bos.flush();
                        bos.close();
                    }


                else {
                    //save image
                    decode_pdf.getObjectStore().saveStoredImage(output_dir + image_name, image_to_save, true, imageType);
                }
            }

            //flush images in case we do more than 1 page so only contains
            //images from current page
            decode_pdf.flushObjectValues(true);
            //flush any text data read

        }
    }
    //////////////////////////////////////////////////////////////////////////

    /**
     * main routine which checks  parameters passed in and runs the conversion
     */
    public static void main(final String[] args) {

        //long start=System.currentTimeMillis();

        System.out.println("Simple demo to extract images from a page");

        //check values first and exit with info if too many
        final int count = args.length;
        final boolean failed = count < || count > 4;

        if (failed) {

            if (count > 0) {
                System.out.println("wrong arguments entered");
                System.out.println("2-4 values expected - 1. file \n 2. output location for images\n 3. output image type (png, tiff, jpeg) (optional)\n 4. Scaling (optional)");

                final StringBuilder arguments = new StringBuilder();
                for (final String arg : args) {
                    arguments.append(arg).append('\n');
                }
                System.out.println("you entered:\n" + arguments + "as the arguments");
            }
        else {

            try {
                if (count == 2) {
                    writeAllPagesAsImagesToDir(args[0], args[1]"png"1.33f);
                else if (count == 4) {
                    writeAllPagesAsImagesToDir(args[0], args[1], args[2]new Float(args[3]));
                else {
                    String s = args[2].toLowerCase();
                    if (s.endsWith("g"|| s.endsWith("f")) {
                        writeAllPagesAsImagesToDir(args[0], args[1], s, 1.33f);
                    else {
                        writeAllPagesAsImagesToDir(args[0], args[1]"png"new Float(s));
                    }
                }

                //System.out.println("Took="+(System.currentTimeMillis()-start)/1000);
            catch (final PdfException ex) {
                throw new RuntimeException(ex.getMessage());
            }
        }
    }

    /**
     * test to see if string or number
     */
    private static boolean isNumber(final String value) {

        //assume true and see if proved wrong
        boolean isNumber = true;

        final int charCount = value.length();
        for (int i = 0; i < charCount; i++) {
            final char c = value.charAt(i);
            if ((c < '0'(c > '9')) {
                isNumber = false;
                i = charCount;
            }
        }

        return isNumber;
    }

    @Override
    void init() {

        //mappings for non-embedded fonts to use
        FontMappings.setFontReplacements();

        type = ExtractTypes.RASTERIZED_PAGE;

        super.init();

        decode_pdf.setExtractionMode(0, pageScaling);
    }

    /**
     @param page                    Logical page number in PDF (first page is 1)
     @param isBackgroundTransparent defines if BufferedImage has a white or transparent background
     @return BufferedImage of PDF page
     @throws PdfException is any issues decoding PDF file
     */
    public BufferedImage getPageAsImage(final int page, final boolean isBackgroundTransparentthrows PdfException {

        checkFileOpened();

        if (!isBackgroundTransparent) {
            return decode_pdf.getPageAsImage(page);
        else //use this if you want a transparent image
            return decode_pdf.getPageAsTransparentImage(page);

        }
    }
}