Merge branch 'subtree'
[fanfix.git] / src / be / nikiroo / fanfix / output / Epub.java
index f27391b4c1e7b6583304bdcb4b05ff45e2ac1850..fc2dc8c982127210c308d6a9dc70a20b667162eb 100644 (file)
@@ -1,12 +1,15 @@
 package be.nikiroo.fanfix.output;
 
+import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileWriter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URL;
-
-import javax.imageio.ImageIO;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.bundles.Config;
@@ -14,25 +17,26 @@ import be.nikiroo.fanfix.bundles.StringId;
 import be.nikiroo.fanfix.data.Chapter;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Paragraph;
-import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.utils.IOUtils;
 import be.nikiroo.utils.StringUtils;
 
 class Epub extends BasicOutput {
        private File tmpDir;
-       private FileWriter writer;
+       private BufferedWriter writer;
        private boolean inDialogue = false;
        private boolean inNormal = false;
        private File images;
+       private boolean nextParaIsCover = true;
 
        @Override
        public File process(Story story, File targetDir, String targetName)
                        throws IOException {
                String targetNameOrig = targetName;
-               targetName += getDefaultExtension();
+               targetName += getDefaultExtension(false);
 
-               tmpDir = File.createTempFile("fanfic-reader-epub_", ".wip");
+               tmpDir = Instance.getInstance().getTempFiles().createTempDir("fanfic-reader-epub");
                tmpDir.delete();
 
                if (!tmpDir.mkdir()) {
@@ -40,25 +44,61 @@ class Epub extends BasicOutput {
                                        "Cannot create a temporary directory: no space left on device?");
                }
 
-               // "Originals"
-               File data = new File(tmpDir, "DATA");
-               data.mkdir();
-               new InfoText().process(story, data, targetNameOrig);
-               IOUtils.writeSmallFile(data, "version", "3.0");
-
                super.process(story, targetDir, targetNameOrig);
 
-               // zip/epub
-               File epub = new File(targetDir, targetName);
-               IOUtils.zip(tmpDir, epub, true);
-               IOUtils.deltree(tmpDir);
-               tmpDir = null;
+               File epub = null;
+               try {
+                       // "Originals"
+                       File data = new File(tmpDir, "DATA");
+                       data.mkdir();
+                       BasicOutput.getOutput(OutputType.TEXT, isWriteInfo(),
+                                       isWriteCover()).process(story, data, targetNameOrig);
+                       InfoCover.writeInfo(data, targetNameOrig, story.getMeta());
+                       IOUtils.writeSmallFile(data, "version", "3.0");
+
+                       // zip/epub
+                       epub = new File(targetDir, targetName);
+                       IOUtils.zip(tmpDir, epub, true);
+
+                       OutputStream out = new FileOutputStream(epub);
+                       try {
+                               ZipOutputStream zip = new ZipOutputStream(out);
+                               try {
+                                       // "mimetype" MUST be the first element and not compressed
+                                       zip.setLevel(ZipOutputStream.STORED);
+                                       File mimetype = new File(tmpDir, "mimetype");
+                                       IOUtils.writeSmallFile(tmpDir, "mimetype",
+                                                       "application/epub+zip");
+                                       ZipEntry entry = new ZipEntry("mimetype");
+                                       entry.setExtra(new byte[] {});
+                                       zip.putNextEntry(entry);
+                                       FileInputStream in = new FileInputStream(mimetype);
+                                       try {
+                                               IOUtils.write(in, zip);
+                                       } finally {
+                                               in.close();
+                                       }
+                                       IOUtils.deltree(mimetype);
+                                       zip.setLevel(ZipOutputStream.DEFLATED);
+                                       //
+
+                                       IOUtils.zip(zip, "", tmpDir, true);
+                               } finally {
+                                       zip.close();
+                               }
+                       } finally {
+                               out.close();
+                       }
+               } finally {
+                       IOUtils.deltree(tmpDir);
+                       tmpDir = null;
+               }
 
                return epub;
        }
 
        @Override
-       public String getDefaultExtension() {
+       public String getDefaultExtension(boolean readerTarget) {
                return ".epub";
        }
 
@@ -73,11 +113,8 @@ class Epub extends BasicOutput {
                File metaInf = new File(tmpDir, "META-INF");
                metaInf.mkdirs();
 
-               // "root"
-               IOUtils.writeSmallFile(tmpDir, "mimetype", "application/epub+zip");
-
                // META-INF
-               String containerContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+               String containerContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                + "<container xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\" version=\"1.0\">\n"
                                + "\t<rootfiles>\n"
                                + "\t\t<rootfile full-path=\"OPS/epb.opf\" media-type=\"application/oebps-package+xml\"/>\n"
@@ -98,16 +135,18 @@ class Epub extends BasicOutput {
 
                // OPS/images
                if (story.getMeta() != null && story.getMeta().getCover() != null) {
-                       String format = Instance.getConfig()
-                                       .getString(Config.IMAGE_FORMAT_COVER).toLowerCase();
-                       File file = new File(images, "cover." + format);
-                       ImageIO.write(story.getMeta().getCover(), format, file);
+                       File file = new File(images, "cover");
+                       try {
+                               Instance.getInstance().getCache().saveAsImage(story.getMeta().getCover(), file, true);
+                       } catch (Exception e) {
+                               Instance.getInstance().getTraceHandler().error(e);
+                       }
                }
 
                // OPS/* except chapters
                IOUtils.writeSmallFile(ops, "epb.ncx", generateNcx(story));
                IOUtils.writeSmallFile(ops, "epb.opf", generateOpf(story));
-               IOUtils.writeSmallFile(ops, "title.xml", generateTitleXml(story));
+               IOUtils.writeSmallFile(ops, "title.xhtml", generateTitleXml(story));
 
                // Resume
                if (story.getMeta() != null && story.getMeta().getResume() != null) {
@@ -118,8 +157,10 @@ class Epub extends BasicOutput {
        @Override
        protected void writeChapterHeader(Chapter chap) throws IOException {
                String filename = String.format("%s%03d%s", "chapter-",
-                               chap.getNumber(), ".xml");
-               writer = new FileWriter(new File(tmpDir + "/OPS", filename));
+                               chap.getNumber(), ".xhtml");
+               writer = new BufferedWriter(new OutputStreamWriter(
+                               new FileOutputStream(new File(tmpDir + File.separator + "OPS",
+                                               filename)), "UTF-8"));
                inDialogue = false;
                inNormal = false;
                try {
@@ -130,9 +171,9 @@ class Epub extends BasicOutput {
                                nameOrNum = chap.getName();
                        }
 
-                       writer.write("<?xml version='1.0' encoding='UTF-8'?>");
-                       writer.write("\n<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>");
-                       writer.write("\n<html xmlns='http://www.w3.org/1999/xhtml' lang='en' xml:lang='en'>");
+                       writer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+                       writer.append("\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
+                       writer.append("\n<html xmlns=\"http://www.w3.org/1999/xhtml\">");
                        writer.write("\n<head>");
                        writer.write("\n        <title>" + StringUtils.xmlEscape(title)
                                        + "</title>");
@@ -194,7 +235,7 @@ class Epub extends BasicOutput {
                        writer.write("          <div class='blank'></div>");
                        break;
                case BREAK:
-                       writer.write("          <hr/>");
+                       writer.write("          <hr class='break'/>");
                        break;
                case NORMAL:
                        writer.write("          <span class='normal'>");
@@ -204,11 +245,13 @@ class Epub extends BasicOutput {
                        break;
                case IMAGE:
                        File file = new File(images, getCurrentImageBestName(false));
-                       Instance.getCache().saveAsImage(new URL(para.getContent()), file);
-                       writer.write("                  <img class='page-image' src='images/"
+                       Instance.getInstance().getCache().saveAsImage(para.getContentImage(), file, nextParaIsCover);
+                       writer.write("                  <img alt='page image' class='page-image' src='images/"
                                        + getCurrentImageBestName(false) + "'/>");
                        break;
                }
+
+               nextParaIsCover = false;
        }
 
        @Override
@@ -262,7 +305,7 @@ class Epub extends BasicOutput {
                        title = meta.getTitle();
                }
 
-               builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+               builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
                builder.append("\n<!DOCTYPE ncx");
                builder.append("\nPUBLIC \"-//NISO//DTD ncx 2005-1//EN\" \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">");
                builder.append("\n<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">");
@@ -290,7 +333,7 @@ class Epub extends BasicOutput {
                builder.append("\n                      <navLabel>");
                builder.append("\n                              <text>Title Page</text>");
                builder.append("\n                      </navLabel>");
-               builder.append("\n                      <content src=\"title.xml\"/>");
+               builder.append("\n                      <content src=\"title.xhtml\"/>");
                builder.append("\n              </navPoint>");
 
                int navPoint = 2; // 1 is above
@@ -313,11 +356,10 @@ class Epub extends BasicOutput {
        private void generateNcx(Chapter chap, StringBuilder builder, int navPoint) {
                String name;
                if (chap.getName() != null && !chap.getName().isEmpty()) {
-                       name = Instance.getTrans().getString(StringId.CHAPTER_NAMED,
-                                       chap.getNumber(), chap.getName());
+                       name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(),
+                                       chap.getName());
                } else {
-                       name = Instance.getTrans().getString(StringId.CHAPTER_UNNAMED,
-                                       chap.getNumber());
+                       name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber());
                }
 
                String nnn = String.format("%03d", (navPoint - 2));
@@ -327,7 +369,7 @@ class Epub extends BasicOutput {
                builder.append("\n                      <navLabel>");
                builder.append("\n                              <text>" + name + "</text>");
                builder.append("\n                      </navLabel>");
-               builder.append("\n                      <content src=\"chapter-" + nnn + ".xml\"/>");
+               builder.append("\n                      <content src=\"chapter-" + nnn + ".xhtml\"/>");
                builder.append("\n              </navPoint>\n");
        }
 
@@ -354,9 +396,8 @@ class Epub extends BasicOutput {
                        lang = meta.getLang();
                }
 
-               builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-               builder.append("\n<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\""
-                               + uuid + "\" version=\"2.0\">");
+               builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+               builder.append("\n<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"BookId\" version=\"2.0\">");
                builder.append("\n   <metadata xmlns:opf=\"http://www.idpf.org/2007/opf\"");
                builder.append("\n             xmlns:dc=\"http://purl.org/dc/elements/1.1/\">");
                builder.append("\n      <dc:title>" + StringUtils.xmlEscape(title)
@@ -374,20 +415,20 @@ class Epub extends BasicOutput {
                builder.append("\n      <dc:source>" + StringUtils.xmlEscape(source)
                                + "</dc:source>");
                builder.append("\n      <dc:rights>Not for commercial use.</dc:rights>");
-               builder.append("\n      <dc:identifier id=\"id\" opf:scheme=\"URI\">"
+               builder.append("\n      <dc:identifier id=\"BookId\" opf:scheme=\"URI\">"
                                + StringUtils.xmlEscape(uuid) + "</dc:identifier>");
                builder.append("\n      <dc:language>" + StringUtils.xmlEscape(lang)
                                + "</dc:language>");
                builder.append("\n   </metadata>");
                builder.append("\n   <manifest>");
                builder.append("\n      <!-- Content Documents -->");
-               builder.append("\n      <item id=\"titlepage\" href=\"title.xml\" media-type=\"application/xhtml+xml\"/>");
+               builder.append("\n      <item id=\"titlepage\" href=\"title.xhtml\" media-type=\"application/xhtml+xml\"/>");
                for (int i = 0; i <= story.getChapters().size(); i++) {
                        String name = String.format("%s%03d", "chapter-", i);
                        builder.append("\n      <item id=\""
                                        + StringUtils.xmlEscapeQuote(name) + "\" href=\""
                                        + StringUtils.xmlEscapeQuote(name)
-                                       + ".xml\" media-type=\"application/xhtml+xml\"/>");
+                                       + ".xhtml\" media-type=\"application/xhtml+xml\"/>");
                }
 
                builder.append("\n      <!-- CSS Style Sheets -->");
@@ -396,8 +437,9 @@ class Epub extends BasicOutput {
                builder.append("\n      <!-- Images -->");
 
                if (story.getMeta() != null && story.getMeta().getCover() != null) {
-                       String format = Instance.getConfig()
-                                       .getString(Config.IMAGE_FORMAT_COVER).toLowerCase();
+                       String format = Instance.getInstance().getConfig()
+                                       .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER)
+                                       .toLowerCase();
                        builder.append("\n      <item id=\"cover\" href=\"images/cover."
                                        + format + "\" media-type=\"image/png\"/>");
                }
@@ -442,12 +484,12 @@ class Epub extends BasicOutput {
                        author = meta.getAuthor();
                }
 
-               String format = Instance.getConfig()
-                               .getString(Config.IMAGE_FORMAT_COVER).toLowerCase();
+               String format = Instance.getInstance().getConfig()
+                               .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase();
 
-               builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-               builder.append("\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd \">");
-               builder.append("\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">");
+               builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+               builder.append("\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
+               builder.append("\n<html xmlns=\"http://www.w3.org/1999/xhtml\">");
                builder.append("\n<head>");
                builder.append("\n      <title>" + StringUtils.xmlEscape(title) + "</title>");
                builder.append("\n      <link rel=\"stylesheet\" href=\"css/style.css\" type=\"text/css\"/>");
@@ -458,7 +500,8 @@ class Epub extends BasicOutput {
                builder.append("\n                      <div class=\"type\">"
                                + StringUtils.xmlEscape(tags) + "</div>");
                builder.append("\n              <div class=\"cover\">");
-               builder.append("\n                      <img src=\"images/cover." + format + "\"></img>");
+               builder.append("\n                      <img alt=\"cover image\" src=\"images/cover."
+                               + format + "\"></img>");
                builder.append("\n              </div>");
                builder.append("\n              <div class=\"author\">"
                                + StringUtils.xmlEscape(author) + "</div>");