+++ /dev/null
-package be.nikiroo.fanfix.output;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-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.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;
-
- @Override
- public File process(Story story, File targetDir, String targetName)
- throws IOException {
- String targetNameOrig = targetName;
- targetName += getDefaultExtension(false);
-
- 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
- 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();
-
- // "root"
- IOUtils.writeSmallFile(tmpDir, "mimetype", "application/epub+zip");
-
- // META-INF
- 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"
- + "\t</rootfiles>\n" + "</container>\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 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.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.write("\n<head>");
- writer.write("\n <title>" + StringUtils.xmlEscape(title)
- + "</title>");
- writer.write("\n <link rel='stylesheet' href='css/style.css' type='text/css'/>");
- writer.write("\n</head>");
- writer.write("\n<body>");
- writer.write("\n <h2>");
- writer.write("\n <span class='chap'>Chapter <span class='chapnumber'>"
- + chap.getNumber() + "</span>:</span> ");
- writer.write("\n <span class='chaptitle'>"
- + StringUtils.xmlEscape(nameOrNum) + "</span>");
- writer.write("\n </h2>");
- writer.write("\n ");
- writer.write("\n <div class='chapter_content'>\n");
- } catch (Exception e) {
- writer.close();
- throw new IOException(e);
- }
- }
-
- @Override
- protected void writeChapterFooter(Chapter chap) throws IOException {
- try {
- if (inDialogue) {
- writer.write(" </div>\n");
- inDialogue = false;
- }
- if (inNormal) {
- writer.write(" </div>\n");
- inNormal = false;
- }
- writer.write(" </div>\n</body>\n</html>\n");
- } finally {
- writer.close();
- writer = null;
- }
- }
-
- @Override
- protected void writeParagraphHeader(Paragraph para) throws IOException {
- if (para.getType() == ParagraphType.QUOTE && !inDialogue) {
- writer.write(" <div class='dialogues'>\n");
- inDialogue = true;
- } else if (para.getType() != ParagraphType.QUOTE && inDialogue) {
- writer.write(" </div>\n");
- inDialogue = false;
- }
-
- if (para.getType() == ParagraphType.NORMAL && !inNormal) {
- writer.write(" <div class='normals'>\n");
- inNormal = true;
- } else if (para.getType() != ParagraphType.NORMAL && inNormal) {
- writer.write(" </div>\n");
- inNormal = false;
- }
-
- switch (para.getType()) {
- case BLANK:
- writer.write(" <div class='blank'></div>");
- break;
- case BREAK:
- writer.write(" <hr class='break'/>");
- break;
- case NORMAL:
- writer.write(" <span class='normal'>");
- break;
- case QUOTE:
- writer.write(" <div class='dialogue'>— ");
- 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/"
- + getCurrentImageBestName(false) + "'/>");
- break;
- }
- }
-
- @Override
- protected void writeParagraphFooter(Paragraph para) throws IOException {
- switch (para.getType()) {
- case NORMAL:
- writer.write("</span>\n");
- break;
- case QUOTE:
- writer.write("</div>\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 "<strong>" + word + "</strong>";
- }
-
- @Override
- protected String italize(String word) {
- return "<emph>" + word + "</emph>";
- }
-
- 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("<?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\">");
- builder.append("\n <head>");
- builder.append("\n <!--The following four metadata items are required for all");
- builder.append("\n NCX documents, including those conforming to the relaxed");
- builder.append("\n constraints of OPS 2.0-->");
- builder.append("\n <meta name=\"dtb:uid\" content=\""
- + StringUtils.xmlEscapeQuote(uuid) + "\"/>");
- builder.append("\n <meta name=\"dtb:depth\" content=\"1\"/>");
- builder.append("\n <meta name=\"dtb:totalPageCount\" content=\"0\"/>");
- builder.append("\n <meta name=\"dtb:maxPageNumber\" content=\"0\"/>");
- builder.append("\n <meta name=\"epub-creator\" content=\""
- + StringUtils.xmlEscapeQuote(EPUB_CREATOR) + "\"/>");
- builder.append("\n </head>");
- builder.append("\n <docTitle>");
- builder.append("\n <text>" + StringUtils.xmlEscape(title) + "</text>");
- builder.append("\n </docTitle>");
- builder.append("\n <docAuthor>");
-
- builder.append("\n <text>" + StringUtils.xmlEscape(author) + "</text>");
- builder.append("\n </docAuthor>");
- builder.append("\n <navMap>");
- builder.append("\n <navPoint id=\"navpoint-1\" playOrder=\"1\">");
- 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 </navPoint>");
-
- 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 </navMap>");
- builder.append("\n</ncx>\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 <navPoint id=\"navpoint-" + navPoint
- + "\" playOrder=\"" + navPoint + "\">");
- 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 </navPoint>\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("<?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("\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)
- + "</dc:title>");
- builder.append("\n <dc:creator opf:role=\"aut\" opf:file-as=\""
- + StringUtils.xmlEscapeQuote(author) + "\">"
- + StringUtils.xmlEscape(author) + "</dc:creator>");
- builder.append("\n <dc:date opf:event=\"original-publication\">"
- + StringUtils.xmlEscape(date) + "</dc:date>");
- builder.append("\n <dc:publisher>"
- + StringUtils.xmlEscape(publisher) + "</dc:publisher>");
- builder.append("\n <dc:date opf:event=\"epub-publication\"></dc:date>");
- builder.append("\n <dc:subject>" + StringUtils.xmlEscape(subject)
- + "</dc:subject>");
- 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\">"
- + 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\"/>");
- 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\"/>");
- }
-
- builder.append("\n <!-- CSS Style Sheets -->");
- builder.append("\n <item id=\"style-css\" href=\"css/style.css\" media-type=\"text/css\"/>");
-
- builder.append("\n <!-- Images -->");
-
- if (story.getMeta() != null && story.getMeta().getCover() != null) {
- String format = Instance.getConfig()
- .getString(Config.IMAGE_FORMAT_COVER).toLowerCase();
- builder.append("\n <item id=\"cover\" href=\"images/cover."
- + format + "\" media-type=\"image/png\"/>");
- }
-
- builder.append("\n <!-- NCX -->");
- builder.append("\n <item id=\"ncx\" href=\"epb.ncx\" media-type=\"application/x-dtbncx+xml\"/>");
- builder.append("\n </manifest>");
- builder.append("\n <spine toc=\"ncx\">");
- builder.append("\n <itemref idref=\"titlepage\" linear=\"yes\"/>");
- for (int i = 0; i <= story.getChapters().size(); i++) {
- String name = String.format("%s%03d", "chapter-", i);
- builder.append("\n <itemref idref=\""
- + StringUtils.xmlEscapeQuote(name) + "\" linear=\"yes\"/>");
- }
- builder.append("\n </spine>");
- builder.append("\n</package>\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("<?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("\n<head>");
- builder.append("\n <title>" + StringUtils.xmlEscape(title) + "</title>");
- builder.append("\n <link rel=\"stylesheet\" href=\"css/style.css\" type=\"text/css\"/>");
- builder.append("\n</head>");
- builder.append("\n<body>");
- builder.append("\n <div class=\"titlepage\">");
- builder.append("\n <h1>" + StringUtils.xmlEscape(title) + "</h1>");
- 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 </div>");
- builder.append("\n <div class=\"author\">"
- + StringUtils.xmlEscape(author) + "</div>");
- builder.append("\n </div>");
- builder.append("\n</body>");
- builder.append("\n</html>\n");
-
- return builder.toString();
- }
-}