com.github.naofum.epubconverter.Convert.java Source code

Java tutorial

Introduction

Here is the source code for com.github.naofum.epubconverter.Convert.java

Source

/*
Copyright 2011 Ezio Querini
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.github.naofum.epubconverter;

import com.github.naofum.epubconverter.R;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;

public class Convert extends Activity {
    private static StringBuilder progressSb;
    private static ScrollView progressSv;
    private static TextView progressTv;
    private static Button okBt;
    private static Button stopBt;
    private static boolean okBtEnabled = true;
    private static boolean conversionStarted = false;
    private static boolean notificationSent = false;
    private static int result;
    private static String filename = "";

    private boolean includeImages;
    private boolean repeatedImages;
    private int pagesPerFile;
    private int onError;
    private boolean addMarkers;
    private boolean hideNotifi;
    private boolean tocFromPdf;

    private final String PDF_EXT = ".pdf";
    private final String EPUB_EXT = " - epubconverter.epub";
    private final String TEMP_EXT = " - epubconverter.tmp";
    private final String OLD_EXT = " - epubconverter.old";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_PROGRESS);
        setProgressBarVisibility(true);
        setContentView(R.layout.progressview);
        setProgress(0);

        // Set variables
        progressSv = (ScrollView) findViewById(R.id.scroll);
        progressTv = (TextView) findViewById(R.id.progress);
        okBt = (Button) findViewById(R.id.ok);
        okBt.setOnClickListener(mOkListener);
        stopBt = (Button) findViewById(R.id.stop);
        stopBt.setOnClickListener(mStopListener);

        if (conversionStarted) {
            // Update screen
            progressTv.setText(progressSb);
            setButtons(okBtEnabled);
        } else if (!notificationSent) {
            // Get filename
            Bundle extras = getIntent().getExtras();
            if (extras != null) {
                if (extras.containsKey("filename")) {
                    String pdfFilename = extras.getString("filename");
                    filename = pdfFilename.substring(0, pdfFilename.lastIndexOf(PDF_EXT));
                    new convertTask().execute();
                }
            }
        }

        // Remove notification
        ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(R.string.app_name);
        notificationSent = false;
    }

    // Get preferences
    @Override
    public void onResume() {
        super.onResume();

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

        includeImages = prefs.getBoolean("include_images", true);
        repeatedImages = prefs.getBoolean("repeated_images", false);
        pagesPerFile = Integer.parseInt(prefs.getString("page_per_file", "5"));
        onError = Integer.parseInt(prefs.getString("on_error", "1"));
        addMarkers = prefs.getBoolean("add_markers", true);
        hideNotifi = prefs.getBoolean("hide_notifi", false);
        tocFromPdf = prefs.getBoolean("toc_from_pdf", true);
    }

    // Set buttons state
    private void setButtons(boolean flag) {
        okBtEnabled = flag;
        okBt.setEnabled(okBtEnabled);
        stopBt.setEnabled(!okBtEnabled);
    }

    // Ok button pressed
    private OnClickListener mOkListener = new OnClickListener() {
        public void onClick(View v) {
            conversionStarted = false;
            progressSb = null;
            progressSv = null;
            progressTv = null;
            finish();
        }
    };

    // Stop button pressed
    private OnClickListener mStopListener = new OnClickListener() {
        public void onClick(View v) {
            result = 5;
        }
    };

    // Back button pressed
    @Override
    public void onBackPressed() {
        conversionStarted = working();
        finish();
    }

    // Conversion in progress?
    public static boolean working() {
        return !okBtEnabled;
    }

    // Conversion started?
    public static boolean started() {
        return conversionStarted;
    }

    // Keep file dialog
    @Override
    protected Dialog onCreateDialog(int id) {
        if (id == 0) {
            // Build dialog
            return new AlertDialog.Builder(Convert.this)
                    .setTitle(getResources().getString(R.string.extraction_error))
                    .setMessage(getResources().getString(R.string.keep))
                    // Ok action
                    .setPositiveButton(getResources().getString(R.string.ok),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int whichButton) {
                                    keepEpub();
                                }
                            })
                    // Cancel action
                    .setNegativeButton(getResources().getString(R.string.cancel),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int whichButton) {
                                    progressSb.append(
                                            "\n" + getResources().getStringArray(R.array.message)[4] + "\n");
                                    deleteTmp();
                                }
                            })
                    // Preview action
                    .setNeutralButton(getResources().getString(R.string.preview),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int whichButton) {
                                    Intent preview = new Intent(getBaseContext(), Preview.class);
                                    preview.putExtra("filename", filename + TEMP_EXT);
                                    startActivityForResult(preview, 0);
                                }
                            })
                    .create();
        } else
            return null;
    }

    // Show dialog again after preview activity
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        showDialog(0);
    }

    // Delete file
    private void deleteTmp() {
        new File(filename + TEMP_EXT).delete();
        if (new File(filename + OLD_EXT).exists()) {
            new File(filename + OLD_EXT).renameTo(new File(filename + EPUB_EXT));
            progressSb.append(getResources().getString(R.string.kept_old));
        } else {
            progressSb.append(getResources().getString(R.string.deleted));
        }
        progressTv.setText(progressSb);
        scroll_up();
    }

    // Keep file
    private void keepEpub() {
        progressSb.append("\n" + getResources().getStringArray(R.array.message)[0] + "\n");
        if (addMarkers) {
            String pageNumberString = String.format(getResources().getString(R.string.pagenumber), ">>\n");
            progressSb.append(String.format(getResources().getString(R.string.errors), "<<@") + pageNumberString);
            progressSb
                    .append(String.format(getResources().getString(R.string.lost_pages), "<<#") + pageNumberString);
        }
        renameFile();
        progressSb.append(getResources().getString(R.string.file) + filename + EPUB_EXT);
        progressTv.setText(progressSb);
        scroll_up();
    }

    // Rename tmp file
    private void renameFile() {
        new File(filename + TEMP_EXT).renameTo(new File(filename + EPUB_EXT));
        new File(filename + OLD_EXT).delete();
    }

    // Scroll scroll view up
    private void scroll_up() {
        progressSv.post(new Runnable() {
            public void run() {
                progressSv.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });
    }

    // Send notification
    public void sendNotification() {
        if (!hideNotifi) {
            NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Convert.class), 0);
            String message = getResources().getStringArray(R.array.message)[result];
            String tickerText = getResources().getString(R.string.app_name);
            //         Notification notif = new Notification(R.drawable.ic_launcher, message, System.currentTimeMillis());
            //         notif.setLatestEventInfo(this, tickerText, message, contentIntent);
            //         nm.notify(R.string.app_name, notif);
            NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext());
            builder.setContentIntent(contentIntent);
            builder.setSmallIcon(R.drawable.ic_launcher);
            builder.setTicker(tickerText);
            builder.setContentText(message);
            builder.setWhen(System.currentTimeMillis());
            NotificationManagerCompat manager = NotificationManagerCompat.from(getApplicationContext());
            manager.notify(R.string.app_name, builder.build());
        }
        notificationSent = true;
    }

    // Start background task
    private class convertTask extends AsyncTask<Void, String, Void> {
        // Background task
        @Override
        protected Void doInBackground(Void... params) {
            // Remove bad files
            String path = filename.substring(0, filename.lastIndexOf('/', filename.length()) + 1);
            String[] files = new File(path).list();
            if ((files != null) && (files.length > 0)) {
                for (int i = 0; i < files.length; i++) {
                    if (files[i].endsWith(TEMP_EXT) || files[i].endsWith(OLD_EXT)) {
                        new File(path + files[i]).delete();
                    }
                }
            }

            // Save old ePUB
            if (new File(filename + EPUB_EXT).exists()) {
                new File(filename + EPUB_EXT).renameTo(new File(filename + OLD_EXT));
            }

            // Load PDF
            publishProgress(String.format(getResources().getString(R.string.load), filename + PDF_EXT));
            if (!(new File(filename + PDF_EXT).exists())) {
                // PDF file not found
                result = 1;
            } else if (ReadPdf.open(filename + PDF_EXT)) {
                // Failed to read PDF file
                result = 2;
            } else if (result != 5) {
                result = fillEpub();
            }

            return null;
        }

        // Update screen
        @Override
        protected void onProgressUpdate(String... messageArray) {
            for (String message : messageArray) {
                progressSb.append(message + "\n");
            }
            progressTv.setText(progressSb);
            scroll_up();
        }

        // Prepare background task
        @Override
        protected void onPreExecute() {
            progressSb = new StringBuilder();
            progressSb.append(getResources().getString(R.string.heading));
            progressSb.append(getResources().getString(R.string.library));
            setButtons(false);
            result = 0;
            conversionStarted = true;
        }

        // Background task ended
        @Override
        protected void onPostExecute(Void params) {
            if (result == 4) {
                if (onError == 0) {
                    // Keep ePUB with errors
                    keepEpub();
                    result = 0;
                } else if (onError == 2) {
                    // Drop ePUB with errors
                    deleteTmp();
                } else {
                    // Ask for keeping ePUB with errors
                    if (!isFinishing()) {
                        showDialog(0);
                    } else {
                        keepEpub();
                        result = 0;
                    }
                }
            } else {
                // Delete on failure
                //            publishProgress("\n" + getResources().getStringArray(R.array.message)[result]);
                progressSb.append("\n" + getResources().getStringArray(R.array.message)[result]);
                progressTv.setText(progressSb);
                scroll_up();
                if (result > 0) {
                    deleteTmp();
                } else {
                    // Keep if ok
                    renameFile();
                    //               publishProgress(String.format(getResources().getString(R.string.file), filename + EPUB_EXT));
                    progressSb.append(String.format(getResources().getString(R.string.file), filename + EPUB_EXT));
                    progressTv.setText(progressSb);
                    scroll_up();
                }
            }

            if (isFinishing()) {
                // Send notification
                sendNotification();
            }

            // Enable ok, disable stop
            setButtons(true);
        }

        // Fill ePUB file
        private int fillEpub() {
            try {
                // Stopped?
                if (result == 5) {
                    return 5;
                }

                // Set up counter
                int pages = ReadPdf.getPages();
                publishProgress(String.format(getResources().getString(R.string.pages), pages));
                int totalFiles = 2 + pages;
                int writedFiles = 0;

                // Set flag
                boolean extractionErrorFlag = false;

                // Create ePUB file
                publishProgress(getResources().getString(R.string.open));
                if (WriteZip.create(filename + TEMP_EXT)) {
                    return 3;
                }

                // Add required files
                publishProgress(getResources().getString(R.string.mimetype));
                setProgress(++writedFiles * 9999 / totalFiles);
                if (WriteZip.addText("mimetype", "application/epub+zip", true)) {
                    return 3;
                }

                publishProgress(getResources().getString(R.string.container));
                if (WriteZip.addText("META-INF/container.xml", createContainer(), false)) {
                    return 3;
                }

                String name = filename.substring(filename.lastIndexOf("/") + 1, filename.length());
                String title = name.replaceAll("[^\\p{Alnum}]", " ");
                String bookId = title + " - " + new Date().hashCode();

                publishProgress(getResources().getString(R.string.toc));
                if (WriteZip.addText("OEBPS/toc.ncx", createToc(pages, bookId, title), false)) {
                    return 3;
                }

                // Add frontpage
                publishProgress(getResources().getString(R.string.frontpage));
                if (WriteZip.addText("OEBPS/frontpage.html", createFrontpage(), false)) {
                    return 3;
                }

                publishProgress(getResources().getString(R.string.frontpagepng));
                if (createFrontpagePng()) {
                    return 3;
                }

                // Add extracted text and images
                List<String> allImageList = new ArrayList<String>();
                for (int i = 1; i <= pages; i += pagesPerFile) {
                    StringBuilder textSb = new StringBuilder();

                    publishProgress(String.format(getResources().getString(R.string.html), i));
                    int endPage = i + pagesPerFile - 1;
                    if (endPage > pages) {
                        endPage = pages;
                    }

                    for (int j = i; j <= endPage; j++) {
                        // Stopped?
                        if (result == 5) {
                            return 5;
                        }

                        // Update progress bar
                        setProgress(++writedFiles * 9999 / totalFiles);

                        // Add anchor
                        textSb.append("  <p>\n");
                        textSb.append("  <a id=\"page" + j + "\"/>\n");

                        // extract text
                        String page = stringToHTMLString(ReadPdf.extractText(j));
                        if (page.length() == 0) {
                            publishProgress(
                                    String.format(getResources().getString(R.string.extraction_failure), j));
                            extractionErrorFlag = true;
                            if (addMarkers) {
                                textSb.append("&lt;&lt;#" + j + "&gt;&gt;");
                            }
                        } else {
                            if (page.matches(".*\\p{Cntrl}.*")) {
                                extractionErrorFlag = true;
                                if (addMarkers) {
                                    textSb.append(page.replaceAll("\\p{Cntrl}+", "&lt;&lt;@" + j + "&gt;&gt;"));
                                } else {
                                    textSb.append(page.replaceAll("\\p{Cntrl}+", " "));
                                }
                            } else {
                                textSb.append(page);
                            }
                        }

                        // extract images
                        if (includeImages) {
                            List<String> imageList = ReadPdf.getImages(j);
                            Iterator<String> iterator = imageList.iterator();
                            while (iterator.hasNext()) {
                                // Stopped?
                                if (result == 5) {
                                    return 5;
                                }

                                String imageName = iterator.next();
                                String imageTag = "\n<img alt=\"" + imageName + "\" src=\"" + imageName
                                        + "\" /><br/>";

                                if (!allImageList.contains(imageName)) {
                                    allImageList.add(imageName);
                                    publishProgress(
                                            String.format(getResources().getString(R.string.image), imageName));
                                    textSb.append(imageTag);
                                } else if (repeatedImages) {
                                    textSb.append(imageTag);
                                }
                            }
                        }
                        // Close page
                        textSb.append("\n  </p>\n");
                    }

                    String text = textSb.toString();
                    if (WriteZip.addText("OEBPS/page" + i + ".html",
                            createHtml("page" + i, text.replaceAll("<br/>(?=[a-z])", "&nbsp;")), false)) {
                        return 3;
                    }
                }

                // Add content.opf
                publishProgress(getResources().getString(R.string.content));
                setProgress(++writedFiles * 9999 / totalFiles);
                if (WriteZip.addText("OEBPS/content.opf", createContent(pages, bookId, allImageList, title),
                        false)) {
                    return 3;
                }

                // Close ePUB file
                publishProgress(getResources().getString(R.string.close));
                if (WriteZip.close()) {
                    return 3;
                }

                if (extractionErrorFlag) {
                    return 4;
                } else {
                    return 0;
                }
            } catch (OutOfMemoryError e) {
                return 6;
            }
        }

        // Create container.xml
        private String createContainer() {
            StringBuilder container = new StringBuilder();
            container.append("<?xml version=\"1.0\"?>\n");
            container.append(
                    "<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n");
            container.append("   <rootfiles>\n");
            container.append(
                    "      <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n");
            container.append("   </rootfiles>\n");
            container.append("</container>\n");
            return container.toString();
        }

        // Create content.opf
        private String createContent(int pages, String id, Iterable<String> images, String title) {
            StringBuilder content = new StringBuilder();
            content.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            content.append(
                    "<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"BookID\" version=\"2.0\">\n");
            content.append(
                    "    <metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:opf=\"http://www.idpf.org/2007/opf\">\n");
            content.append("        <dc:title>" + title + "</dc:title>\n");
            content.append(
                    "        <dc:creator>" + (ReadPdf.getAuthor()).replaceAll("[<>]", "_") + "</dc:creator>\n");
            content.append(
                    "        <dc:creator opf:role=\"bkp\">epubconverter - Minimal offline PDF to ePUB converter for Android</dc:creator>\n");
            content.append("        <dc:identifier id=\"BookID\" opf:scheme=\"UUID\">" + id + "</dc:identifier>\n");
            content.append("        <dc:language>en</dc:language>\n");
            content.append("    </metadata>\n");
            content.append("    <manifest>\n");
            for (int i = 1; i <= pages; i += pagesPerFile) {
                content.append("        <item id=\"page" + i + "\" href=\"page" + i
                        + ".html\" media-type=\"application/xhtml+xml\"/>\n");
            }
            content.append("        <item id=\"ncx\" href=\"toc.ncx\" media-type=\"application/x-dtbncx+xml\"/>\n");
            content.append(
                    "        <item id=\"frontpage\" href=\"frontpage.html\" media-type=\"application/xhtml+xml\"/>\n");
            content.append("        <item id=\"cover\" href=\"frontpage.png\" media-type=\"image/png\"/>\n");
            for (String name : images) {
                content.append("        <item id=\"" + name + "\" href=\"" + name + "\" media-type=\"image/"
                        + name.substring(name.lastIndexOf('.') + 1) + "\"/>\n");
            }
            content.append("    </manifest>\n");
            content.append("    <spine toc=\"ncx\">\n");
            content.append("        <itemref idref=\"frontpage\"/>\n");
            for (int i = 1; i <= pages; i += pagesPerFile) {
                content.append("        <itemref idref=\"page" + i + "\"/>\n");
            }
            content.append("    </spine>\n");
            content.append("    <guide>\n");
            content.append("        <reference type=\"cover\" title=\"Frontpage\" href=\"frontpage.html\"/>\n");
            content.append("    </guide>\n");
            content.append("</package>\n");
            return content.toString();
        }

        // Create toc.ncx
        private String createToc(int pages, String id, String title) {
            StringBuilder toc = new StringBuilder();
            toc.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            toc.append("<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"\n");
            toc.append("   \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n");
            toc.append("<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">\n");
            toc.append("    <head>\n");
            toc.append("        <meta name=\"dtb:uid\" content=\"" + id + "\"/>\n");
            toc.append("        <meta name=\"dtb:depth\" content=\"1\"/>\n");
            toc.append("        <meta name=\"dtb:totalPageCount\" content=\"0\"/>\n");
            toc.append("        <meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n");
            toc.append("    </head>\n");
            toc.append("    <docTitle>\n");
            toc.append("        <text>" + title + "</text>\n");
            toc.append("    </docTitle>\n");
            toc.append("    <navMap>\n");
            toc.append("        <navPoint id=\"navPoint-1\" playOrder=\"1\">\n");
            toc.append("            <navLabel>\n");
            toc.append("                <text>Frontpage</text>\n");
            toc.append("            </navLabel>\n");
            toc.append("            <content src=\"frontpage.html\"/>\n");
            toc.append("        </navPoint>\n");

            int playOrder = 2;
            boolean extractedToc = false;
            if (tocFromPdf) {
                // Try to extract toc from pdf
                XMLParser parser = new XMLParser();
                Document doc = parser.getDomElement(ReadPdf.getBookmarks());
                if (doc != null) {
                    doc.normalize();
                    NodeList nl = doc.getElementsByTagName("Title");
                    if (nl != null) {
                        int lastPage = Integer.MAX_VALUE;
                        StringBuilder sb = new StringBuilder();
                        // looping through all item nodes <item>
                        for (int i = 0; i < nl.getLength(); i++) {
                            Element e = (Element) nl.item(i);
                            String action = parser.getValue(e, "Action");
                            if (action.equals("GoTo")) {
                                String chapter = parser.getElementValue(e).trim();
                                try {
                                    int page = Integer.valueOf(parser.getValue(e, "Page").split(" ")[0]);

                                    // First entry not in page one, create a dummy one
                                    if ((lastPage == Integer.MAX_VALUE) && (page > 1)) {
                                        sb.append(title);
                                        sb.append("\n");
                                        lastPage = 1;
                                    }

                                    // Add entry in toc
                                    if (page > lastPage) {
                                        int pageFile = ((int) ((lastPage - 1) / pagesPerFile)) * pagesPerFile + 1;
                                        toc.append("        <navPoint id=\"navPoint-" + playOrder
                                                + "\" playOrder=\"" + playOrder + "\">\n");
                                        toc.append("            <navLabel>\n");
                                        toc.append("                <text>" + sb.toString()
                                                + "                </text>\n");
                                        toc.append("            </navLabel>\n");
                                        toc.append("            <content src=\"page" + pageFile + ".html#page"
                                                + lastPage + "\"/>\n");
                                        toc.append("        </navPoint>\n");
                                        playOrder += 1;

                                        sb = new StringBuilder();
                                    }

                                    // Set next entry
                                    sb.append(chapter);
                                    sb.append("\n");
                                    lastPage = page;
                                } catch (RuntimeException ex) {
                                    System.err.println("RuntimeException in xml extraction " + ex.getMessage());
                                }
                            }
                            extractedToc = true;
                        }

                        // Add last entry
                        if (sb.length() > 0) {
                            int pageFile = ((int) ((lastPage - 1) / pagesPerFile)) * pagesPerFile + 1;
                            toc.append("        <navPoint id=\"navPoint-" + playOrder + "\" playOrder=\""
                                    + playOrder + "\">\n");
                            toc.append("            <navLabel>\n");
                            toc.append("                <text>" + sb.toString() + "                </text>\n");
                            toc.append("            </navLabel>\n");
                            toc.append("            <content src=\"page" + pageFile + ".html#page" + lastPage
                                    + "\"/>\n");
                            toc.append("        </navPoint>\n");
                        }

                    }
                }
            }

            // Create dummy toc
            if (!extractedToc) {
                for (int i = 1; i <= pages; i += pagesPerFile) {
                    toc.append("        <navPoint id=\"navPoint-" + playOrder + "\" playOrder=\"" + playOrder
                            + "\">\n");
                    toc.append("            <navLabel>\n");
                    toc.append("                <text>Page" + i + "</text>\n");
                    toc.append("            </navLabel>\n");
                    toc.append("            <content src=\"page" + i + ".html\"/>\n");
                    toc.append("        </navPoint>\n");
                    playOrder += 1;
                }
            }

            toc.append("    </navMap>\n");
            toc.append("</ncx>\n");
            return toc.toString();
        }

        // Create html
        private String createHtml(String title, String body) {
            StringBuilder html = new StringBuilder();
            html.append("  <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \n");
            html.append("  \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n");
            html.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");
            html.append("<head>\n");
            html.append("  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/>\n");
            html.append(
                    "  <meta name=\"generator\" content=\"epubconverter - Minimal offline PDF to ePUB converter for Android\"/>\n");
            html.append("  <title>" + title + "</title>\n");
            html.append("</head>\n");
            html.append("<body>\n");
            html.append(body);
            html.append("\n</body>\n");
            html.append("</html>\n");
            return html.toString();
        }

        // Create frontpage.html
        private String createFrontpage() {
            return createHtml("Frontpage", "<div><img alt=\"cover\" src=\"frontpage.png\" /></div>");
        }

        // Create frontpage.png
        private boolean createFrontpagePng() {
            final int maxWidth = 300;
            final int maxHeight = 410;
            final int border = 10;
            final int fontsize = 48;

            // Grey background
            Bitmap bmp = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.RGB_565);
            Paint paint = new Paint();
            paint.setColor(Color.LTGRAY);
            Canvas canvas = new Canvas(bmp);
            canvas.drawRect(0, 0, maxWidth, maxHeight, paint);

            // Add epubconverter logo
            Bitmap img = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
            canvas.drawBitmap(img, maxWidth - img.getWidth(), maxHeight - img.getHeight(),
                    new Paint(Paint.FILTER_BITMAP_FLAG));

            // Add title
            paint.setTextSize(fontsize);
            paint.setColor(Color.BLACK);
            paint.setAntiAlias(true);
            paint.setStyle(Paint.Style.FILL);

            String name = filename.substring(filename.lastIndexOf("/") + 1, filename.length()).replaceAll("_", " ");
            String words[] = name.split("\\s");

            float newline = paint.getFontSpacing();
            float x = border;
            float y = newline;

            for (String word : words) {
                float len = paint.measureText(word + " ");

                // Line wrap
                if ((x > border) && ((x + len) > maxWidth)) {
                    x = border;
                    y += newline;
                }

                // Word wrap
                while ((x + len) > maxWidth) {
                    int maxChar = (int) (word.length() * (maxWidth - border - x) / paint.measureText(word));
                    canvas.drawText(word.substring(0, maxChar), x, y, paint);
                    word = word.substring(maxChar);
                    len = paint.measureText(word + " ");
                    x = border;
                    y += newline;
                }

                canvas.drawText(word, x, y, paint);
                x += len;
            }

            // Save bmp as png
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
            return WriteZip.addImage("OEBPS/frontpage.png", baos.toByteArray());
        }

        //  stringToHTMLString found on the web, no license indicated
        //  http://www.rgagnon.com/javadetails/java-0306.html
        //   Author: S. Bayer.
        private String stringToHTMLString(String string) {
            StringBuilder sb = new StringBuilder(); // changed StringBuffer to StringBuilder to prevent buffer overflow (iiizio)
            // true if last char was blank
            boolean lastWasBlankChar = false;
            int len = string.length();
            char c;

            for (int i = 0; i < len; i++) {
                c = string.charAt(i);
                if (c == ' ') {
                    // blank gets extra work,
                    // this solves the problem you get if you replace all
                    // blanks with &nbsp;, if you do that you loss 
                    // word breaking
                    if (lastWasBlankChar) {
                        lastWasBlankChar = false;
                        sb.append("&nbsp;");
                    } else {
                        lastWasBlankChar = true;
                        sb.append(' ');
                    }
                } else {
                    lastWasBlankChar = false;
                    //
                    // HTML Special Chars
                    if (c == '"')
                        sb.append("&quot;");
                    else if (c == '&')
                        sb.append("&amp;");
                    else if (c == '<')
                        sb.append("&lt;");
                    else if (c == '>')
                        sb.append("&gt;");
                    else if (c == '%') // Android browser doesn't like % (iiizio)
                        sb.append("&#37;");
                    else if (c == '\n')
                        // Handle Newline
                        sb.append("\n<br/>");
                    else {
                        int ci = 0xffff & c;
                        if (ci < 160) {
                            // nothing special only 7 Bit
                            sb.append(c);
                        } else {
                            // Not 7 Bit use the unicode system
                            sb.append("&#");
                            sb.append(Integer.valueOf(ci).toString());
                            sb.append(';');
                        }
                    }
                }
            }
            return sb.toString();
        }
    }
}