package be.nikiroo.fanfix.output; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.net.URL; import javax.imageio.ImageIO; 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.Story; import be.nikiroo.fanfix.data.Paragraph.ParagraphType; import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.StringUtils; class Epub extends BasicOutput { private File tmpDir; private FileWriter writer; private boolean inDialogue = false; private boolean inNormal = false; private File images; @Override public File process(Story story, File targetDir, String targetName) throws IOException { String targetNameOrig = targetName; targetName += getDefaultExtension(); tmpDir = File.createTempFile("fanfic-reader-epub_", ".wip"); tmpDir.delete(); if (!tmpDir.mkdir()) { throw new IOException( "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; return epub; } @Override protected String getDefaultExtension() { 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(); // "root" IOUtils.writeSmallFile(tmpDir, "mimetype", "application/epub+zip"); // 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) { String format = Instance.getConfig() .getString(Config.IMAGE_FORMAT_COVER).toLowerCase(); File file = new File(images, "cover." + format); ImageIO.write(story.getMeta().getCover(), format, file); } // OPS/* except chapters IOUtils.writeSmallFile(ops, "epb.ncx", generateNcx(story)); IOUtils.writeSmallFile(ops, "epb.opf", generateOpf(story)); IOUtils.writeSmallFile(ops, "title.xml", 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(), ".xml"); writer = new FileWriter(new File(tmpDir + "/OPS", filename)); 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.write(""); writer.write("\n"); writer.write("\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.getCache().saveAsImage(new URL(para.getContent()), file); writer.write(" "); break; } } @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.getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), chap.getName()); } else { name = Instance.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.getConfig() .getString(Config.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.getConfig() .getString(Config.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 "); builder.append("\n
"); builder.append("\n
" + StringUtils.xmlEscape(author) + "
"); builder.append("\n
"); builder.append("\n"); builder.append("\n\n"); return builder.toString(); } }