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("
");
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
");
builder.append("\n
");
builder.append("\n
"
+ StringUtils.xmlEscape(author) + "
");
builder.append("\n
");
builder.append("\n");
builder.append("\n\n");
return builder.toString();
}
}