package be.nikiroo.fanfix.output; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; 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; 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.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 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(false); tmpDir = Instance.getInstance().getTempFiles().createTempDir("fanfic-reader-epub"); tmpDir.delete(); if (!tmpDir.mkdir()) { throw new IOException( "Cannot create a temporary directory: no space left on device?"); } super.process(story, targetDir, targetNameOrig); 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(boolean readerTarget) { return ".epub"; } @Override protected void writeStoryHeader(Story story) throws IOException { File ops = new File(tmpDir, "OPS"); ops.mkdirs(); File css = new File(ops, "css"); css.mkdirs(); images = new File(ops, "images"); images.mkdirs(); File metaInf = new File(tmpDir, "META-INF"); metaInf.mkdirs(); // META-INF String containerContent = "\n" + "\n" + "\t\n" + "\t\t\n" + "\t\n" + "\n"; IOUtils.writeSmallFile(metaInf, "container.xml", containerContent); // OPS/css InputStream inStyle = getClass().getResourceAsStream("epub.style.css"); if (inStyle == null) { throw new IOException("Cannot find style.css resource"); } try { IOUtils.write(inStyle, new File(css, "style.css")); } finally { inStyle.close(); } // OPS/images if (story.getMeta() != null && story.getMeta().getCover() != null) { 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.xhtml", generateTitleXml(story)); // Resume if (story.getMeta() != null && story.getMeta().getResume() != null) { writeChapter(story.getMeta().getResume()); } } @Override protected void writeChapterHeader(Chapter chap) throws IOException { String filename = String.format("%s%03d%s", "chapter-", chap.getNumber(), ".xhtml"); writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(new File(tmpDir + File.separator + "OPS", filename)), "UTF-8")); inDialogue = false; inNormal = false; try { String title = "Chapter " + chap.getNumber(); String nameOrNum = Integer.toString(chap.getNumber()); if (chap.getName() != null && !chap.getName().isEmpty()) { title += ": " + chap.getName(); nameOrNum = chap.getName(); } writer.append(""); writer.append("\n"); writer.append("\n"); writer.write("\n"); writer.write("\n " + StringUtils.xmlEscape(title) + ""); writer.write("\n "); writer.write("\n"); writer.write("\n"); writer.write("\n

"); writer.write("\n Chapter " + chap.getNumber() + ": "); writer.write("\n " + StringUtils.xmlEscape(nameOrNum) + ""); writer.write("\n

"); writer.write("\n "); writer.write("\n
\n"); } catch (Exception e) { writer.close(); throw new IOException(e); } } @Override protected void writeChapterFooter(Chapter chap) throws IOException { try { if (inDialogue) { writer.write("
\n"); inDialogue = false; } if (inNormal) { writer.write(" \n"); inNormal = false; } writer.write(" \n\n\n"); } finally { writer.close(); writer = null; } } @Override protected void writeParagraphHeader(Paragraph para) throws IOException { if (para.getType() == ParagraphType.QUOTE && !inDialogue) { writer.write("
\n"); inDialogue = true; } else if (para.getType() != ParagraphType.QUOTE && inDialogue) { writer.write("
\n"); inDialogue = false; } if (para.getType() == ParagraphType.NORMAL && !inNormal) { writer.write("
\n"); inNormal = true; } else if (para.getType() != ParagraphType.NORMAL && inNormal) { writer.write("
\n"); inNormal = false; } switch (para.getType()) { case BLANK: writer.write("
"); break; case BREAK: writer.write("
"); break; case NORMAL: writer.write(" "); break; case QUOTE: writer.write("
— "); break; case IMAGE: File file = new File(images, getCurrentImageBestName(false)); Instance.getInstance().getCache().saveAsImage(para.getContentImage(), file, nextParaIsCover); writer.write(" page image"); break; } nextParaIsCover = false; } @Override protected void writeParagraphFooter(Paragraph para) throws IOException { switch (para.getType()) { case NORMAL: writer.write("\n"); break; case QUOTE: writer.write("
\n"); break; default: writer.write("\n"); break; } } @Override protected void writeTextLine(ParagraphType type, String line) throws IOException { switch (type) { case QUOTE: case NORMAL: writer.write(decorateText(StringUtils.xmlEscape(line))); break; default: break; } } @Override protected String enbold(String word) { return "" + word + ""; } @Override protected String italize(String word) { return "" + word + ""; } private String generateNcx(Story story) { StringBuilder builder = new StringBuilder(); String title = ""; String uuid = ""; String author = ""; if (story.getMeta() != null) { MetaData meta = story.getMeta(); uuid = meta.getUuid(); author = meta.getAuthor(); title = meta.getTitle(); } builder.append(""); builder.append("\n"); builder.append("\n"); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n " + StringUtils.xmlEscape(title) + ""); builder.append("\n "); builder.append("\n "); builder.append("\n " + StringUtils.xmlEscape(author) + ""); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n Title Page"); builder.append("\n "); builder.append("\n "); builder.append("\n "); int navPoint = 2; // 1 is above if (story.getMeta() != null & story.getMeta().getResume() != null) { Chapter chap = story.getMeta().getResume(); generateNcx(chap, builder, navPoint++); } for (Chapter chap : story) { generateNcx(chap, builder, navPoint++); } builder.append("\n "); builder.append("\n\n"); return builder.toString(); } private void generateNcx(Chapter chap, StringBuilder builder, int navPoint) { String name; if (chap.getName() != null && !chap.getName().isEmpty()) { name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), chap.getName()); } else { name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber()); } String nnn = String.format("%03d", (navPoint - 2)); builder.append("\n "); builder.append("\n "); builder.append("\n " + name + ""); builder.append("\n "); builder.append("\n "); builder.append("\n \n"); } private String generateOpf(Story story) { StringBuilder builder = new StringBuilder(); String title = ""; String uuid = ""; String author = ""; String date = ""; String publisher = ""; String subject = ""; String source = ""; String lang = ""; if (story.getMeta() != null) { MetaData meta = story.getMeta(); title = meta.getTitle(); uuid = meta.getUuid(); author = meta.getAuthor(); date = meta.getDate(); publisher = meta.getPublisher(); subject = meta.getSubject(); source = meta.getSource(); lang = meta.getLang(); } builder.append(""); builder.append("\n"); builder.append("\n "); builder.append("\n " + StringUtils.xmlEscape(title) + ""); builder.append("\n " + StringUtils.xmlEscape(author) + ""); builder.append("\n " + StringUtils.xmlEscape(date) + ""); builder.append("\n " + StringUtils.xmlEscape(publisher) + ""); builder.append("\n "); builder.append("\n " + StringUtils.xmlEscape(subject) + ""); builder.append("\n " + StringUtils.xmlEscape(source) + ""); builder.append("\n Not for commercial use."); builder.append("\n " + StringUtils.xmlEscape(uuid) + ""); builder.append("\n " + StringUtils.xmlEscape(lang) + ""); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); for (int i = 0; i <= story.getChapters().size(); i++) { String name = String.format("%s%03d", "chapter-", i); builder.append("\n "); } builder.append("\n "); builder.append("\n "); builder.append("\n "); if (story.getMeta() != null && story.getMeta().getCover() != null) { String format = Instance.getInstance().getConfig() .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER) .toLowerCase(); builder.append("\n "); } builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); builder.append("\n "); for (int i = 0; i <= story.getChapters().size(); i++) { String name = String.format("%s%03d", "chapter-", i); builder.append("\n "); } builder.append("\n "); builder.append("\n\n"); return builder.toString(); } private String generateTitleXml(Story story) { StringBuilder builder = new StringBuilder(); String title = ""; String tags = ""; String author = ""; if (story.getMeta() != null) { MetaData meta = story.getMeta(); title = meta.getTitle(); if (meta.getTags() != null) { for (String tag : meta.getTags()) { if (!tags.isEmpty()) { tags += ", "; } tags += tag; } if (!tags.isEmpty()) { tags = "(" + tags + ")"; } } author = meta.getAuthor(); } String format = Instance.getInstance().getConfig() .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); builder.append(""); builder.append("\n"); builder.append("\n"); builder.append("\n"); builder.append("\n " + StringUtils.xmlEscape(title) + ""); builder.append("\n "); builder.append("\n"); builder.append("\n"); builder.append("\n
"); builder.append("\n

" + StringUtils.xmlEscape(title) + "

"); builder.append("\n
" + StringUtils.xmlEscape(tags) + "
"); builder.append("\n
"); builder.append("\n \"cover"); builder.append("\n
"); builder.append("\n
" + StringUtils.xmlEscape(author) + "
"); builder.append("\n
"); builder.append("\n"); builder.append("\n\n"); return builder.toString(); } }