4
votes

I'm trying to create a PDF document based on the form that the User is shown in my Android app. I use the following code to create the PDF File which is then attached to an email:

public static File generate(View salesFragmentTableLayout, Context context) throws KingdomSpasException {
        Builder printAttrsBuilder = new Builder();
        printAttrsBuilder.setMediaSize(PrintAttributes.MediaSize.ISO_A4);
        printAttrsBuilder.setMinMargins(new Margins(5, 5, 5, 5));

        PrintedPdfDocument document = new PrintedPdfDocument(context, printAttrsBuilder.build());

        PageInfo pageInfo = new PageInfo.Builder(150, 150, 1).create();
        Page page = document.startPage(pageInfo);
        salesFragmentTableLayout.draw(page.getCanvas());
        document.finishPage(page);
        File result = null;
        try {
            result = File.createTempFile("Kingdom Spas Agreement", ".pdf", context.getCacheDir());
            document.writeTo(new BufferedOutputStream(new FileOutputStream(result)));
        } catch (FileNotFoundException e) {
            throw new KingdomSpasException("Failed to find relevent file", e);
        } catch (IOException e) {
            throw new KingdomSpasException("IO Problem occured while creatin the PDF", e);
        }
        document.close();
        return result;
    }

The resulting PDF is always corrupted and cannot be opened by either Adobe Acroread or GS. When I open it in acroread I get the error:

There was an error opening this document. The file is damaged and could not be repaired.

When I try and open it in GS using the following command:

gs \
   -o repaired.pdf \
   -sDEVICE=pdfwrite \
   -dPDFSETTINGS=/prepress \
  KingdomSpasAgreement.pdf 

I get the following output:

**** Error: Cannot find a 'startxref' anywhere in the file.
**** Warning: An error occurred while reading an XREF table.
**** The file has been damaged. This may have been caused
**** by a problem while converting or transfering the file.
**** Ghostscript will attempt to recover the data.
**** Error: Trailer is not found. No pages will be processed (FirstPage > LastPage).

**** This file had errors that were repaired or ignored.
**** Please notify the author of the software that produced this
**** file that it does not conform to Adobe's published PDF
**** specification.

I don't understand what it is that I could be doing wrong - the whole process seems fairly straightforward, but always fails.

Edit:

Here is the relevant layout for completeness:

<TableLayout
            android:id="@+id/salesAgreementTableLayout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content" >

                <ImageView
                    android:id="@+id/companyLogo"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:contentDescription="@string/logo_description"
                    android:src="@drawable/company_logo" />

                <ImageView
                    android:id="@+id/companyAddress"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:contentDescription="@string/address_description"
                    android:src="@drawable/company_address" />
            </TableRow>

            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical" >

                <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" >

                    <EditText
                        android:id="@+id/salesExecInitials"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:background="@drawable/round"
                        android:inputType="text"
                        android:maxLines="1"
                        android:paddingLeft="130dp"
                        android:singleLine="true" >

                        <requestFocus />
                    </EditText>

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_vertical"
                        android:paddingLeft="10dp"
                        android:text="@string/sales_exec_initials" />
                </FrameLayout>
            </TableRow>
        </TableLayout>

Edit #2: I was checking the file after it had been emailed, but have now checked it directly using "adb pull" and it's still corrupted in the same way.

Edit #3: I've uploaded an example of a corrupted PDF to dropox: Example of Corrupted PDF

It's actually using a much more simplified View, but is still corrupt

Edit# 4: I've now tried calling sync() on the getFD() and also closing the streams as suggested below by CommonsWare this causes an exception, which I find interesting. The stack trace is here in case it sheds any light:

12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950): com.kingdomspas.android.kingdomspasforms.exceptions.KingdomSpasException: Failed to correctly clean up streams
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at com.kingdomspas.android.kingdomspasforms.utils.KingdomSpasFormsPDFGenerator.generate(KingdomSpasFormsPDFGenerator.java:69)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at com.kingdomspas.android.kingdomspasforms.listeners.KingdomSpasSubmitButtonListener.onClick(KingdomSpasSubmitButtonListener.java:25)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at android.view.View.performClick(View.java:4438)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at android.view.View$PerformClick.run(View.java:18439)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at android.os.Handler.handleCallback(Handler.java:733)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at android.os.Handler.dispatchMessage(Handler.java:95)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at android.os.Looper.loop(Looper.java:136)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at android.app.ActivityThread.main(ActivityThread.java:5147)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at java.lang.reflect.Method.invokeNative(Native Method)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at java.lang.reflect.Method.invoke(Method.java:515)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:795)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:611)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at dalvik.system.NativeStart.main(Native Method)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950): Caused by: java.io.IOException: write failed: EBADF (Bad file number)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.IoBridge.write(IoBridge.java:455)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at java.io.FileOutputStream.write(FileOutputStream.java:187)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at java.io.BufferedOutputStream.flushInternal(BufferedOutputStream.java:185)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:85)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at com.kingdomspas.android.kingdomspasforms.utils.KingdomSpasFormsPDFGenerator.generate(KingdomSpasFormsPDFGenerator.java:63)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   ... 12 more
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950): Caused by: libcore.io.ErrnoException: write failed: EBADF (Bad file number)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.Posix.writeBytes(Native Method)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.Posix.write(Posix.java:202)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.BlockGuardOs.write(BlockGuardOs.java:197)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.IoBridge.write(IoBridge.java:450)
12-16 16:39:05.675: E/KingdomSpasSubmitButtonListener(17950):   ... 16 more
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950): Failed to generate the agreement PDF
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950): com.kingdomspas.android.kingdomspasforms.exceptions.KingdomSpasException: Failed to correctly clean up streams
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at com.kingdomspas.android.kingdomspasforms.utils.KingdomSpasFormsPDFGenerator.generate(KingdomSpasFormsPDFGenerator.java:69)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at com.kingdomspas.android.kingdomspasforms.listeners.KingdomSpasSubmitButtonListener.onClick(KingdomSpasSubmitButtonListener.java:25)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at android.view.View.performClick(View.java:4438)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at android.view.View$PerformClick.run(View.java:18439)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at android.os.Handler.handleCallback(Handler.java:733)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at android.os.Handler.dispatchMessage(Handler.java:95)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at android.os.Looper.loop(Looper.java:136)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at android.app.ActivityThread.main(ActivityThread.java:5147)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at java.lang.reflect.Method.invokeNative(Native Method)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at java.lang.reflect.Method.invoke(Method.java:515)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:795)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:611)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at dalvik.system.NativeStart.main(Native Method)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950): Caused by: java.io.IOException: write failed: EBADF (Bad file number)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.IoBridge.write(IoBridge.java:455)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at java.io.FileOutputStream.write(FileOutputStream.java:187)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at java.io.BufferedOutputStream.flushInternal(BufferedOutputStream.java:185)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:85)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at com.kingdomspas.android.kingdomspasforms.utils.KingdomSpasFormsPDFGenerator.generate(KingdomSpasFormsPDFGenerator.java:63)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   ... 12 more
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950): Caused by: libcore.io.ErrnoException: write failed: EBADF (Bad file number)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.Posix.writeBytes(Native Method)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.Posix.write(Posix.java:202)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.BlockGuardOs.write(BlockGuardOs.java:197)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   at libcore.io.IoBridge.write(IoBridge.java:450)
12-16 16:39:13.845: E/KingdomSpasSubmitButtonListener(17950):   ... 16 more
2
When are you checking the file? After having received by email? - greenapps
Yes - how can I check it before? - Paul Hunnisett
You are not really asking me is it. - greenapps
Can you show an example PDF file? Perhaps that can give a clue as to what went wrong with it. - David van Driessche
Can you please provide a (link to a) sample PDF? This would allow to analyse its faults and then tell you in detail what exactly is wrong with your PDF creation code. - Kurt Pfeifle

2 Answers

5
votes

You never flush() nor close() your streams. You should also call sync() on getFD() on your FileOutputStream to ensure that all the bytes are committed to disk (versus being write-cached by the filesystem). Depending upon timing of when you try to do something with the file, this sort of stuff can result in truncated output.

0
votes

I've tested your code, and it always creates an empty document (0 content byte), not a damaged one, so that explains why you can't fix it with gs. About the Android printing framework, I'm not sure you can directly print a document without calling the PrintManager class. All the documentations I've read mention it, and all the sample codes I've seen use it.

There is a good tutorial about custom documents printing on Android developers website, you can check it. A complete example is also available here. I've tested this last sample code and it works well! The only difference is that it displays some UI for selecting options before printing.

Hope you can make it this way!