codenameone

How can a PDF with text and images be generated


The following snippet generates a pdf with text but the image is distorted like this

Seems the required image should be a .jpg. When I used a .png, not even a distorted image was showing in the pdf.

public static void createPdf() {
    try {
        //Image image = Image.createImage("/icon.png");
        Image image = Image.createImage("/Logo.jpg");
        EncodedImage encodedImage = EncodedImage.createFromImage(image, true);
        byte[] imageData = encodedImage.getImageData();

        StringBuilder content = new StringBuilder();
        content.append("%PDF-1.4\n");
        content.append("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
        content.append("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
        content.append("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
        content.append("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R>>>>endobj\n");
        content.append("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
        content.append("6 0 obj<</Length 219>>stream\n");
        content.append("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
        content.append("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
        content.append("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
        content.append("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
        content.append("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
        content.append("512 0 0 512 4 50 cm\n");
        content.append("/Im0 Do\n");
        content.append("endstream\n");
        content.append("endobj\n");
        content.append("7 0 obj<</Type/XObject/Subtype/Image/Width 512/Height 512/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length 132959>>stream\n");
        
        content.append(new String(imageData));

        content.append("endstream\n");
        content.append("endobj\n");
        content.append("\n");
        content.append("xref\n");
        content.append("0 8\n");
        content.append("0000000000 65535 f \n");
        content.append("0000000009 00000 n \n");
        content.append("0000000052 00000 n \n");
        content.append("0000000102 00000 n \n");
        content.append("0000000197 00000 n \n");
        content.append("0000000255 00000 n \n");
        content.append("0000000316 00000 n \n");
        content.append("0000000609 00000 n \n");
        content.append("trailer\n");
        content.append("<</Size 8/Root 1 0 R>>\n");
        content.append("startxref\n");
        content.append("133724\n");
        content.append("%%EOF\n");
        
        FileSystemStorage fss = FileSystemStorage.getInstance();
        String pdfPath = fss.getAppHomePath() + "Test2.pdf";
        try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath))) {
            w.write(content.toString());
        } catch (Exception e) {
            Log.p("Error " + e);
        }
    } catch (Exception e) {
        Log.p("Error " + e);
    }
}

The generated pdf is saved in app storage directory home/.cn1

How can the required raw image data be extracted from the image so that image can show without distortion?

Edit: This question is a bit different from this since I need the image binary data to be generated/created and inserted to the pdf programmatically not manually

EDIT

Writing pdf content directly without appending to a StringBuilder results to this Test7.pdf. That is

private void createPdf7() {
    try {
        FileSystemStorage fss = FileSystemStorage.getInstance();
        String pdfPath = fss.getAppHomePath() + "Test7.pdf";
        try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath))) {

            w.write("%PDF-1.4\n");
            w.write("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
            w.write("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
            w.write("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
            w.write("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R>>>>endobj\n");
            w.write("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
            w.write("6 0 obj<</Length 219>>stream\n");
            w.write("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
            w.write("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
            w.write("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
            w.write("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
            w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
            w.write("512 0 0 512 4 50 cm\n");
            w.write("/Im0 Do\n");
            w.write("endstream\n");
            w.write("endobj\n");
            w.write("7 0 obj<</Type/XObject/Subtype/Image/Width 50/Height 50/ColorSpace/DeviceRGB/BitsPerComponent 8/Length 132959>>stream\n");

            InputStream is = Display.getInstance().getResourceAsStream(this.getClass(), "/Logo.jpg");
            //InputStream is = Display.getInstance().getResourceAsStream(this.getClass(), "/icon.jpeg");
            //InputStream is = Display.getInstance().getResourceAsStream(this.getClass(), "/icon.png");

            int nextChar = is.read();
            if (nextChar == -1) {
                //return null;
            }

            while (nextChar > -1) {
                //Log.p("Char " + (char) nextChar);
                char[] charArray = {(char) nextChar};
                w.write(charArray);
                nextChar = is.read();
            }
            w.write("\nendstream\n");
            w.write("endobj\n");
            w.write("\n");
            w.write("xref\n");
            w.write("0 8\n");
            w.write("0000000000 65535 f \n");
            w.write("0000000009 00000 n \n");
            w.write("0000000052 00000 n \n");
            w.write("0000000102 00000 n \n");
            w.write("0000000197 00000 n \n");
            w.write("0000000255 00000 n \n");
            w.write("0000000316 00000 n \n");
            w.write("0000000609 00000 n \n");
            w.write("trailer\n");
            w.write("<</Size 8/Root 1 0 R>>\n");
            w.write("startxref\n");
            w.write("133724\n");
            w.write("%%EOF\n");

        } catch (Exception e) {
            Log.p("Error " + e);
        }
    } catch (Exception e) {
        Log.p("Error " + e);
    }
}

What I am still missing for the image to show correctly?

EDIT Opening this JSjpegSample-with image.pdf with Linux Text Editor shows the following image data stream encoding. Note some readable text like JFIF Photoshop 7.0 Pdf with image

In Codename One simulator, this encoding is close to how SQLite database file is encoded. SQLite database open with Linux Text Editor shows the following. Note some readable text like SQLite format 3 CREATE TABLE SQLite DB

How can a .jpeg to be added to a pdf be encoded same way as SQLite database file?


Solution

  • The following successfully adds image to the pdf document. The requirements include:

    1. Create a jpeg image using EncodedImage.createFromImage(image, true); This is useful since png images can be converted to jpeg

    2. Create a ByteArrayInputStream from image bytes. Using ByteArrayInputStream, image bytes are directly written as characters to the pdf.

    3. Set ISO-8859-1 encoding in OutputStreamWriter. This encoding ensured all characters in the image bytes are read as expected by Filter/DCTDecode.

      Windows-1252 encoding also tries but some hexadecimal are read as ?.

    
        private void createPdf11() {
            try {
                //Image image = Image.createImage("/Logo.jpg");
                Image image = Image.createImage("/android_logo.png");
                //Image image = Image.createImage("/ios_logo.png");
                
                //create a jpeg encoded image
                EncodedImage encodedImage = EncodedImage.createFromImage(image, true);
                byte[] imageData = encodedImage.getImageData();
    
                //get image width, height & length
                int imageWidth = encodedImage.getWidth();
                int imageHeight = encodedImage.getHeight();
                int imageLength = imageData.length;
                
                //create ByteArrayInputStream from image byte array
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);
    
                FileSystemStorage fss = FileSystemStorage.getInstance();
                String pdfPath = fss.getAppHomePath() + "Test12.pdf";
                try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath), "ISO-8859-1")) {
                    w.write("%PDF-1.7\n");
                    w.write("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
                    w.write("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
                    w.write("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
                    w.write("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R>>>>endobj\n");
                    w.write("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
                    w.write("6 0 obj<</Length 219>>stream\n");
                    w.write("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
                    w.write("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
                    w.write("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
                    w.write("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
                    w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
                    w.write("50 0 0 50 4 540 cm\n");
                    w.write("/Im0 Do\n");
                    w.write("endstream\n");
                    w.write("endobj\n");
                    w.write("7 0 obj<</Type/XObject/Subtype/Image/Width " + imageWidth + "/Height " + imageHeight + "/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length " + imageLength + ">>stream\n");
    
                    //read the next byte of data from ByteArrayInputStream in int range 0 - 255
                    int nextChar = byteArrayInputStream.read();
                    while (nextChar > -1) {
                        //convert int to char and add it to char array
                        char[] charArray = {(char) nextChar};
                        //write char array
                        w.write(charArray);
                        nextChar = byteArrayInputStream.read();
                    }
    
                    w.write("\nendstream\n");
                    w.write("endobj\n");
                    w.write("\n");
                    w.write("xref\n");
                    w.write("0 8\n");
                    w.write("0000000000 65535 f \n");
                    w.write("0000000009 00000 n \n");
                    w.write("0000000052 00000 n \n");
                    w.write("0000000102 00000 n \n");
                    w.write("0000000197 00000 n \n");
                    w.write("0000000255 00000 n \n");
                    w.write("0000000316 00000 n \n");
                    w.write("0000000609 00000 n \n");
                    w.write("trailer\n");
                    w.write("<</Size 8/Root 1 0 R>>\n");
                    w.write("startxref\n");
                    w.write("133724\n");
                    w.write("%%EOF\n");
    
                } catch (Exception e) {
                    Log.p("Error " + e);
                }
            } catch (Exception e) {
                Log.p("Error " + e);
            }
        }
    
    
    

    This creates Test11.pdf with text and image in the app home path directory home/.cn1

    enter image description here

    To add a 2nd image, another object to contain it eg 8 0 obj is needed. This object number is then added to 4 0 obj as /Im1 8 0 R. Finally the position of 2nd image is also set in 6 0 obj as w.write("q 50 0 0 50 4 460 cm /Im1 Do Q\n");. That is,

    private void createPdf13() {
        try {
            Image image = Image.createImage("/Logo.jpg");
            //create a jpeg encoded image
            EncodedImage encodedImage = EncodedImage.createFromImage(image, true);
            byte[] imageData = encodedImage.getImageData();
    
            int imageWidth = encodedImage.getWidth();
            int imageHeight = encodedImage.getHeight();
            int imageLength = imageData.length;
            //create ByteArrayInputStream from image byte array
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);
    
            Image image2 = Image.createImage("/android_logo.png");
            //create a jpeg encoded image
            EncodedImage encodedImage2 = EncodedImage.createFromImage(image2, true);
            byte[] imageData2 = encodedImage2.getImageData();
            int imageWidth2 = encodedImage2.getWidth();
            int imageHeight2 = encodedImage2.getHeight();
            int imageLength2 = imageData2.length;
            //create ByteArrayInputStream from image byte array
            ByteArrayInputStream byteArrayInputStream2 = new ByteArrayInputStream(imageData2);
    
            FileSystemStorage fss = FileSystemStorage.getInstance();
            String pdfPath = fss.getAppHomePath() + "Test13.pdf";
            try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath), "ISO-8859-1")) {
                w.write("%PDF-1.7\n");
                w.write("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
                w.write("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
                w.write("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
                w.write("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R/Im1 8 0 R>>>>endobj\n");
                w.write("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
                w.write("6 0 obj<</Length 219>>stream\n");
                w.write("q\n");
                w.write("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
                w.write("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
                w.write("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
                w.write("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
                w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
                w.write("q 50 0 0 50 4 540 cm /Im0 Do Q\n");
                w.write("q 50 0 0 50 4 460 cm /Im1 Do Q\n");
                w.write("Q\n");
                w.write("endstream\n");
                w.write("endobj\n");
    
                w.write("7 0 obj<</Type/XObject/Subtype/Image/Width " + imageWidth + "/Height " + imageHeight + "/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length " + imageLength + ">>stream\n");
                //read the next byte of data from ByteArrayInputStream in int range 0 - 255
                int nextChar = byteArrayInputStream.read();
                while (nextChar > -1) {
                    //convert int to char and add it to char array
                    char[] charArray = {(char) nextChar};
                    //write char array
                    w.write(charArray);
                    nextChar = byteArrayInputStream.read();
                }
                w.write("\nendstream\n");
                w.write("endobj\n");
    
                w.write("8 0 obj<</Type/XObject/Subtype/Image/Width " + imageWidth2 + "/Height " + imageHeight2 + "/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length " + imageLength2 + ">>stream\n");
                //read the next byte of data from ByteArrayInputStream in int range 0 - 255
                int nextChar2 = byteArrayInputStream2.read();
                while (nextChar2 > -1) {
                    //convert int to char and add it to char array
                    char[] charArray2 = {(char) nextChar2};
                    //write char array
                    w.write(charArray2);
                    nextChar2 = byteArrayInputStream2.read();
                }
                w.write("\nendstream\n");
                w.write("endobj\n");
    
                w.write("\n");
                w.write("xref\n");
                w.write("0 9\n");
                w.write("0000000000 65535 f \n");
                w.write("0000000009 00000 n \n");
                w.write("0000000052 00000 n \n");
                w.write("0000000102 00000 n \n");
                w.write("0000000197 00000 n \n");
                w.write("0000000255 00000 n \n");
                w.write("0000000316 00000 n \n");
                w.write("0000000609 00000 n \n");
                w.write("trailer\n");
                w.write("<</Size 9/Root 1 0 R>>\n");
                w.write("startxref\n");
                w.write("133724\n");
                w.write("%%EOF\n");
    
            } catch (Exception e) {
                Log.p("Error " + e);
            }
        } catch (Exception e) {
            Log.p("Error " + e);
        }
    }
    

    Which generates Test13.pdf