Fix some CBZ cover/fake cover issues
[fanfix.git] / src / be / nikiroo / fanfix / output / BasicOutput.java
CommitLineData
08fe2e33
NR
1package be.nikiroo.fanfix.output;
2
3import java.io.File;
4import java.io.IOException;
bee7dffe
NR
5import java.util.ArrayList;
6import java.util.List;
08fe2e33
NR
7
8import be.nikiroo.fanfix.Instance;
9import be.nikiroo.fanfix.bundles.StringId;
10import be.nikiroo.fanfix.data.Chapter;
11import be.nikiroo.fanfix.data.Paragraph;
08fe2e33 12import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
bee7dffe 13import be.nikiroo.fanfix.data.Story;
3b2b638f 14import be.nikiroo.utils.Progress;
08fe2e33
NR
15
16/**
17 * This class is the base class used by the other output classes. It can be used
18 * outside of this package, and have static method that you can use to get
19 * access to the correct support class.
20 *
21 * @author niki
22 */
23public abstract class BasicOutput {
24 /**
25 * The supported output types for which we can get a {@link BasicOutput}
26 * object.
27 *
28 * @author niki
29 */
30 public enum OutputType {
31 /** EPUB files created with this program */
32 EPUB,
33 /** Pure text file with some rules */
34 TEXT,
35 /** TEXT but with associated .info file */
36 INFO_TEXT,
37 /** DEBUG output to console */
38 SYSOUT,
39 /** ZIP with (PNG) images */
40 CBZ,
41 /** LaTeX file with "book" template */
2206ef66
NR
42 LATEX,
43 /** HTML files in a dedicated directory */
44 HTML,
45
46 ;
47
211f7ddb 48 @Override
08fe2e33
NR
49 public String toString() {
50 return super.toString().toLowerCase();
51 }
52
53 /**
54 * A description of this output type.
55 *
4d205683
NR
56 * @param longDesc
57 * TRUE for the long description, FALSE for the short one
58 *
08fe2e33
NR
59 * @return the description
60 */
4d205683
NR
61 public String getDesc(boolean longDesc) {
62 StringId id = longDesc ? StringId.OUTPUT_DESC
63 : StringId.OUTPUT_DESC_SHORT;
64
65 String desc = Instance.getTrans().getStringX(id, this.name());
08fe2e33
NR
66
67 if (desc == null) {
4d205683 68 desc = Instance.getTrans().getString(id, this);
08fe2e33
NR
69 }
70
b2612f9d
NR
71 if (desc == null) {
72 desc = this.toString();
73 }
74
08fe2e33
NR
75 return desc;
76 }
77
2206ef66
NR
78 /**
79 * The default extension to add to the output files.
80 *
10d558d2
NR
81 * @param readerTarget
82 * the target to point to to read the {@link Story} (for
83 * instance, the main entry point if this {@link Story} is in
84 * a directory bundle)
85 *
2206ef66
NR
86 * @return the extension
87 */
10d558d2 88 public String getDefaultExtension(boolean readerTarget) {
925298fd 89 BasicOutput output = BasicOutput.getOutput(this, false, false);
2206ef66 90 if (output != null) {
10d558d2 91 return output.getDefaultExtension(readerTarget);
2206ef66
NR
92 }
93
94 return null;
95 }
96
08fe2e33 97 /**
0efd25e3
NR
98 * Call {@link OutputType#valueOf(String)} after conversion to upper
99 * case.
08fe2e33
NR
100 *
101 * @param typeName
102 * the possible type name
103 *
104 * @return NULL or the type
105 */
106 public static OutputType valueOfUC(String typeName) {
107 return OutputType.valueOf(typeName == null ? null : typeName
108 .toUpperCase());
109 }
110
111 /**
0efd25e3 112 * Call {@link OutputType#valueOf(String)} after conversion to upper
e604986c 113 * case but return def for NULL and empty instead of raising an
0efd25e3 114 * exception.
08fe2e33
NR
115 *
116 * @param typeName
117 * the possible type name
e604986c
NR
118 * @param def
119 * the default value
08fe2e33
NR
120 *
121 * @return NULL or the type
122 */
e604986c 123 public static OutputType valueOfNullOkUC(String typeName, OutputType def) {
a6395bef 124 if (typeName == null || typeName.isEmpty()) {
e604986c 125 return def;
08fe2e33
NR
126 }
127
128 return OutputType.valueOfUC(typeName);
129 }
130
131 /**
0efd25e3 132 * Call {@link OutputType#valueOf(String)} after conversion to upper
e604986c 133 * case but return def in case of error instead of raising an exception.
08fe2e33
NR
134 *
135 * @param typeName
136 * the possible type name
e604986c
NR
137 * @param def
138 * the default value
08fe2e33
NR
139 *
140 * @return NULL or the type
141 */
e604986c 142 public static OutputType valueOfAllOkUC(String typeName, OutputType def) {
08fe2e33
NR
143 try {
144 return OutputType.valueOfUC(typeName);
145 } catch (Exception e) {
e604986c 146 return def;
08fe2e33
NR
147 }
148 }
149 }
150
151 /** The creator name (this program, by me!) */
152 static final String EPUB_CREATOR = "Fanfix (by Niki)";
153
154 /** The current best name for an image */
155 private String imageName;
156 private File targetDir;
157 private String targetName;
158 private OutputType type;
159 private boolean writeCover;
160 private boolean writeInfo;
bee7dffe
NR
161 private Progress storyPg;
162 private Progress chapPg;
08fe2e33
NR
163
164 /**
165 * Process the {@link Story} into the given target.
166 *
167 * @param story
168 * the {@link Story} to export
169 * @param target
170 * the target where to save to (will not necessary be taken as is
171 * by the processor, for instance an extension can be added)
bee7dffe
NR
172 * @param pg
173 * the optional progress reporter
08fe2e33
NR
174 *
175 * @return the actual main target saved, which can be slightly different
176 * that the input one
177 *
178 * @throws IOException
179 * in case of I/O error
180 */
bee7dffe
NR
181 public File process(Story story, String target, Progress pg)
182 throws IOException {
183 storyPg = pg;
184
d5a7153c
NR
185 File targetDir = null;
186 String targetName = null;
187 if (target != null) {
188 target = new File(target).getAbsolutePath();
189 targetDir = new File(target).getParentFile();
190 targetName = new File(target).getName();
191
192 String ext = getDefaultExtension(false);
193 if (ext != null && !ext.isEmpty()) {
194 if (targetName.toLowerCase().endsWith(ext)) {
195 targetName = targetName.substring(0, targetName.length()
196 - ext.length());
197 }
08fe2e33
NR
198 }
199 }
200
201 return process(story, targetDir, targetName);
202 }
203
204 /**
205 * Process the {@link Story} into the given target.
206 * <p>
207 * This method is expected to be overridden in most cases.
208 *
209 * @param story
210 * the {@link Story} to export
211 * @param targetDir
212 * the target dir where to save to
213 * @param targetName
214 * the target filename (will not necessary be taken as is by the
215 * processor, for instance an extension can be added)
216 *
bee7dffe 217 *
08fe2e33
NR
218 * @return the actual main target saved, which can be slightly different
219 * that the input one
220 *
221 * @throws IOException
222 * in case of I/O error
223 */
224 protected File process(Story story, File targetDir, String targetName)
225 throws IOException {
226 this.targetDir = targetDir;
227 this.targetName = targetName;
228
229 writeStory(story);
230
231 return null;
232 }
233
234 /**
235 * The output type.
236 *
237 * @return the type
238 */
239 public OutputType getType() {
240 return type;
241 }
242
5083ef84
NR
243 /**
244 * Enable the creation of a .info file next to the resulting processed file.
245 *
246 * @return TRUE to enable it
247 */
248 protected boolean isWriteInfo() {
249 return writeInfo;
250 }
251
252 /**
253 * Enable the creation of a cover file next to the resulting processed file
254 * if possible.
255 *
256 * @return TRUE to enable it
257 */
258 protected boolean isWriteCover() {
259 return writeCover;
260 }
261
08fe2e33
NR
262 /**
263 * The output type.
264 *
265 * @param type
266 * the new type
0efd25e3
NR
267 * @param writeCover
268 * TRUE to enable the creation of a cover if possible
925298fd
NR
269 * @param writeInfo
270 * TRUE to enable the creation of a .info file
08fe2e33
NR
271 *
272 * @return this
273 */
925298fd
NR
274 protected BasicOutput setType(OutputType type, boolean writeInfo,
275 boolean writeCover) {
08fe2e33 276 this.type = type;
08fe2e33 277 this.writeInfo = writeInfo;
925298fd 278 this.writeCover = writeCover;
08fe2e33
NR
279
280 return this;
281 }
282
283 /**
284 * The default extension to add to the output files.
08fe2e33 285 *
10d558d2
NR
286 * @param readerTarget
287 * the target to point to to read the {@link Story} (for
288 * instance, the main entry point if this {@link Story} is in a
289 * directory bundle)
290 *
08fe2e33
NR
291 * @return the extension
292 */
211f7ddb
NR
293 public String getDefaultExtension(
294 @SuppressWarnings("unused") boolean readerTarget) {
08fe2e33
NR
295 return "";
296 }
297
211f7ddb 298 @SuppressWarnings("unused")
08fe2e33
NR
299 protected void writeStoryHeader(Story story) throws IOException {
300 }
301
211f7ddb 302 @SuppressWarnings("unused")
08fe2e33
NR
303 protected void writeChapterHeader(Chapter chap) throws IOException {
304 }
305
211f7ddb 306 @SuppressWarnings("unused")
08fe2e33
NR
307 protected void writeParagraphHeader(Paragraph para) throws IOException {
308 }
309
211f7ddb 310 @SuppressWarnings("unused")
08fe2e33
NR
311 protected void writeStoryFooter(Story story) throws IOException {
312 }
313
211f7ddb 314 @SuppressWarnings("unused")
08fe2e33
NR
315 protected void writeChapterFooter(Chapter chap) throws IOException {
316 }
317
211f7ddb 318 @SuppressWarnings("unused")
08fe2e33
NR
319 protected void writeParagraphFooter(Paragraph para) throws IOException {
320 }
321
322 protected void writeStory(Story story) throws IOException {
bee7dffe
NR
323 if (storyPg == null) {
324 storyPg = new Progress(0, story.getChapters().size() + 2);
325 } else {
326 storyPg.setMinMax(0, story.getChapters().size() + 2);
327 }
328
08fe2e33
NR
329 String chapterNameNum = String.format("%03d", 0);
330 String paragraphNumber = String.format("%04d", 0);
331 imageName = paragraphNumber + "_" + chapterNameNum + ".png";
332
fe999aa4 333 if (story.getMeta() != null) {
68686a37 334 story.getMeta().setType("" + getType());
fe999aa4 335 }
68686a37 336
925298fd
NR
337 System.out.println(story.getMeta().getTitle() + " -> write cover: "
338 + writeCover);
339 new Exception().printStackTrace();
340
08fe2e33
NR
341 if (writeCover) {
342 InfoCover.writeCover(targetDir, targetName, story.getMeta());
343 }
344 if (writeInfo) {
345 InfoCover.writeInfo(targetDir, targetName, story.getMeta());
346 }
347
bee7dffe
NR
348 storyPg.setProgress(1);
349
350 List<Progress> chapPgs = new ArrayList<Progress>(story.getChapters()
351 .size());
08fe2e33 352 for (Chapter chap : story) {
bee7dffe
NR
353 chapPg = new Progress(0, chap.getParagraphs().size());
354 storyPg.addProgress(chapPg, 1);
355 chapPgs.add(chapPg);
356 chapPg = null;
357 }
358
359 writeStoryHeader(story);
360 for (int i = 0; i < story.getChapters().size(); i++) {
361 chapPg = chapPgs.get(i);
362 writeChapter(story.getChapters().get(i));
363 chapPg.setProgress(chapPg.getMax());
364 chapPg = null;
08fe2e33
NR
365 }
366 writeStoryFooter(story);
bee7dffe
NR
367
368 storyPg.setProgress(storyPg.getMax());
369 storyPg = null;
08fe2e33
NR
370 }
371
372 protected void writeChapter(Chapter chap) throws IOException {
373 String chapterNameNum;
374 if (chap.getName() == null || chap.getName().isEmpty()) {
375 chapterNameNum = String.format("%03d", chap.getNumber());
376 } else {
377 chapterNameNum = String.format("%03d", chap.getNumber()) + "_"
378 + chap.getName().replace(" ", "_");
379 }
380
381 int num = 0;
382 String paragraphNumber = String.format("%04d", num++);
383 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
384
385 writeChapterHeader(chap);
bee7dffe 386 int i = 1;
08fe2e33
NR
387 for (Paragraph para : chap) {
388 paragraphNumber = String.format("%04d", num++);
389 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
390 writeParagraph(para);
bee7dffe
NR
391 if (chapPg != null) {
392 chapPg.setProgress(i++);
393 }
08fe2e33
NR
394 }
395 writeChapterFooter(chap);
396 }
397
398 protected void writeParagraph(Paragraph para) throws IOException {
399 writeParagraphHeader(para);
400 writeTextLine(para.getType(), para.getContent());
401 writeParagraphFooter(para);
402 }
403
211f7ddb 404 @SuppressWarnings("unused")
08fe2e33
NR
405 protected void writeTextLine(ParagraphType type, String line)
406 throws IOException {
407 }
408
409 /**
410 * Return the current best guess for an image name, based upon the current
411 * {@link Chapter} and {@link Paragraph}.
412 *
413 * @param prefix
414 * add the original target name as a prefix
415 *
416 * @return the guessed name
417 */
418 protected String getCurrentImageBestName(boolean prefix) {
419 if (prefix) {
420 return targetName + "_" + imageName;
421 }
422
423 return imageName;
424 }
425
426 /**
427 * Return the given word or sentence as <b>bold</b>.
428 *
429 * @param word
430 * the input
431 *
432 * @return the bold output
433 */
434 protected String enbold(String word) {
435 return word;
436 }
437
438 /**
439 * Return the given word or sentence as <i>italic</i>.
440 *
441 * @param word
442 * the input
443 *
444 * @return the italic output
445 */
446 protected String italize(String word) {
447 return word;
448 }
449
450 /**
451 * Decorate the given text with <b>bold</b> and <i>italic</i> words,
452 * according to {@link BasicOutput#enbold(String)} and
453 * {@link BasicOutput#italize(String)}.
454 *
455 * @param text
456 * the input
457 *
458 * @return the decorated output
459 */
460 protected String decorateText(String text) {
461 StringBuilder builder = new StringBuilder();
462
463 int bold = -1;
464 int italic = -1;
465 char prev = '\0';
466 for (char car : text.toCharArray()) {
467 switch (car) {
468 case '*':
469 if (bold >= 0 && prev != ' ') {
470 String data = builder.substring(bold);
471 builder.setLength(bold);
472 builder.append(enbold(data));
473 bold = -1;
474 } else if (bold < 0
475 && (prev == ' ' || prev == '\0' || prev == '\n')) {
476 bold = builder.length();
477 } else {
478 builder.append(car);
479 }
480
481 break;
482 case '_':
483 if (italic >= 0 && prev != ' ') {
484 String data = builder.substring(italic);
485 builder.setLength(italic);
486 builder.append(enbold(data));
487 italic = -1;
488 } else if (italic < 0
489 && (prev == ' ' || prev == '\0' || prev == '\n')) {
490 italic = builder.length();
491 } else {
492 builder.append(car);
493 }
494
495 break;
496 default:
497 builder.append(car);
498 break;
499 }
500
501 prev = car;
502 }
503
504 if (bold >= 0) {
505 builder.insert(bold, '*');
506 }
507
508 if (italic >= 0) {
509 builder.insert(italic, '_');
510 }
511
512 return builder.toString();
513 }
514
515 /**
516 * Return a {@link BasicOutput} object compatible with the given
517 * {@link OutputType}.
518 *
519 * @param type
520 * the type
925298fd
NR
521 * @param writeCover
522 * TRUE to enable the creation of a cover if possible to be saved
523 * next to the main target file
524 * @param writeInfo
525 * TRUE to enable the creation of a .info file to be saved next
08fe2e33
NR
526 * to the main target file
527 *
528 * @return the {@link BasicOutput}
529 */
925298fd
NR
530 public static BasicOutput getOutput(OutputType type, boolean writeInfo,
531 boolean writeCover) {
08fe2e33
NR
532 if (type != null) {
533 switch (type) {
534 case EPUB:
925298fd 535 return new Epub().setType(type, writeInfo, writeCover);
08fe2e33 536 case TEXT:
925298fd 537 return new Text().setType(type, writeInfo, true);
08fe2e33
NR
538 case INFO_TEXT:
539 return new InfoText().setType(type, true, true);
540 case SYSOUT:
541 return new Sysout().setType(type, false, false);
542 case CBZ:
925298fd 543 return new Cbz().setType(type, writeInfo, writeCover);
08fe2e33 544 case LATEX:
925298fd 545 return new LaTeX().setType(type, writeInfo, writeCover);
2206ef66 546 case HTML:
925298fd 547 return new Html().setType(type, writeInfo, writeCover);
08fe2e33
NR
548 }
549 }
550
551 return null;
552 }
553}