doc
[nikiroo-utils.git] / output / BasicOutput.java
1 package be.nikiroo.fanfix.output;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import be.nikiroo.fanfix.Instance;
9 import be.nikiroo.fanfix.bundles.StringId;
10 import be.nikiroo.fanfix.data.Chapter;
11 import be.nikiroo.fanfix.data.Paragraph;
12 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
13 import be.nikiroo.fanfix.data.Story;
14 import be.nikiroo.utils.Progress;
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 */
23 public 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 */
42 LATEX,
43 /** HTML files in a dedicated directory */
44 HTML,
45
46 ;
47
48 @Override
49 public String toString() {
50 return super.toString().toLowerCase();
51 }
52
53 /**
54 * A description of this output type.
55 *
56 * @param longDesc
57 * TRUE for the long description, FALSE for the short one
58 *
59 * @return the description
60 */
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());
66
67 if (desc == null) {
68 desc = Instance.getTrans().getString(id, this);
69 }
70
71 if (desc == null) {
72 desc = this.toString();
73 }
74
75 return desc;
76 }
77
78 /**
79 * The default extension to add to the output files.
80 *
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 *
86 * @return the extension
87 */
88 public String getDefaultExtension(boolean readerTarget) {
89 BasicOutput output = BasicOutput.getOutput(this, false);
90 if (output != null) {
91 return output.getDefaultExtension(readerTarget);
92 }
93
94 return null;
95 }
96
97 /**
98 * Call {@link OutputType#valueOf(String)} after conversion to upper
99 * case.
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 /**
112 * Call {@link OutputType#valueOf(String)} after conversion to upper
113 * case but return def for NULL and empty instead of raising an
114 * exception.
115 *
116 * @param typeName
117 * the possible type name
118 * @param def
119 * the default value
120 *
121 * @return NULL or the type
122 */
123 public static OutputType valueOfNullOkUC(String typeName, OutputType def) {
124 if (typeName == null || typeName.isEmpty()) {
125 return def;
126 }
127
128 return OutputType.valueOfUC(typeName);
129 }
130
131 /**
132 * Call {@link OutputType#valueOf(String)} after conversion to upper
133 * case but return def in case of error instead of raising an exception.
134 *
135 * @param typeName
136 * the possible type name
137 * @param def
138 * the default value
139 *
140 * @return NULL or the type
141 */
142 public static OutputType valueOfAllOkUC(String typeName, OutputType def) {
143 try {
144 return OutputType.valueOfUC(typeName);
145 } catch (Exception e) {
146 return def;
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;
161 private Progress storyPg;
162 private Progress chapPg;
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)
172 * @param pg
173 * the optional progress reporter
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 */
181 public File process(Story story, String target, Progress pg)
182 throws IOException {
183 storyPg = pg;
184
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 }
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 *
217 *
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
243 /**
244 * The output type.
245 *
246 * @param type
247 * the new type
248 * @param writeInfo
249 * TRUE to enable the creation of a .info file
250 * @param writeCover
251 * TRUE to enable the creation of a cover if possible
252 *
253 * @return this
254 */
255 protected BasicOutput setType(OutputType type, boolean writeCover,
256 boolean writeInfo) {
257 this.type = type;
258 this.writeCover = writeCover;
259 this.writeInfo = writeInfo;
260
261 return this;
262 }
263
264 /**
265 * The default extension to add to the output files.
266 *
267 * @param readerTarget
268 * the target to point to to read the {@link Story} (for
269 * instance, the main entry point if this {@link Story} is in a
270 * directory bundle)
271 *
272 * @return the extension
273 */
274 public String getDefaultExtension(
275 @SuppressWarnings("unused") boolean readerTarget) {
276 return "";
277 }
278
279 @SuppressWarnings("unused")
280 protected void writeStoryHeader(Story story) throws IOException {
281 }
282
283 @SuppressWarnings("unused")
284 protected void writeChapterHeader(Chapter chap) throws IOException {
285 }
286
287 @SuppressWarnings("unused")
288 protected void writeParagraphHeader(Paragraph para) throws IOException {
289 }
290
291 @SuppressWarnings("unused")
292 protected void writeStoryFooter(Story story) throws IOException {
293 }
294
295 @SuppressWarnings("unused")
296 protected void writeChapterFooter(Chapter chap) throws IOException {
297 }
298
299 @SuppressWarnings("unused")
300 protected void writeParagraphFooter(Paragraph para) throws IOException {
301 }
302
303 protected void writeStory(Story story) throws IOException {
304 if (storyPg == null) {
305 storyPg = new Progress(0, story.getChapters().size() + 2);
306 } else {
307 storyPg.setMinMax(0, story.getChapters().size() + 2);
308 }
309
310 String chapterNameNum = String.format("%03d", 0);
311 String paragraphNumber = String.format("%04d", 0);
312 imageName = paragraphNumber + "_" + chapterNameNum + ".png";
313
314 if (story.getMeta() != null) {
315 story.getMeta().setType("" + getType());
316 }
317
318 if (writeCover) {
319 InfoCover.writeCover(targetDir, targetName, story.getMeta());
320 }
321 if (writeInfo) {
322 InfoCover.writeInfo(targetDir, targetName, story.getMeta());
323 }
324
325 storyPg.setProgress(1);
326
327 List<Progress> chapPgs = new ArrayList<Progress>(story.getChapters()
328 .size());
329 for (Chapter chap : story) {
330 chapPg = new Progress(0, chap.getParagraphs().size());
331 storyPg.addProgress(chapPg, 1);
332 chapPgs.add(chapPg);
333 chapPg = null;
334 }
335
336 writeStoryHeader(story);
337 for (int i = 0; i < story.getChapters().size(); i++) {
338 chapPg = chapPgs.get(i);
339 writeChapter(story.getChapters().get(i));
340 chapPg.setProgress(chapPg.getMax());
341 chapPg = null;
342 }
343 writeStoryFooter(story);
344
345 storyPg.setProgress(storyPg.getMax());
346 storyPg = null;
347 }
348
349 protected void writeChapter(Chapter chap) throws IOException {
350 String chapterNameNum;
351 if (chap.getName() == null || chap.getName().isEmpty()) {
352 chapterNameNum = String.format("%03d", chap.getNumber());
353 } else {
354 chapterNameNum = String.format("%03d", chap.getNumber()) + "_"
355 + chap.getName().replace(" ", "_");
356 }
357
358 int num = 0;
359 String paragraphNumber = String.format("%04d", num++);
360 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
361
362 writeChapterHeader(chap);
363 int i = 1;
364 for (Paragraph para : chap) {
365 paragraphNumber = String.format("%04d", num++);
366 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
367 writeParagraph(para);
368 if (chapPg != null) {
369 chapPg.setProgress(i++);
370 }
371 }
372 writeChapterFooter(chap);
373 }
374
375 protected void writeParagraph(Paragraph para) throws IOException {
376 writeParagraphHeader(para);
377 writeTextLine(para.getType(), para.getContent());
378 writeParagraphFooter(para);
379 }
380
381 @SuppressWarnings("unused")
382 protected void writeTextLine(ParagraphType type, String line)
383 throws IOException {
384 }
385
386 /**
387 * Return the current best guess for an image name, based upon the current
388 * {@link Chapter} and {@link Paragraph}.
389 *
390 * @param prefix
391 * add the original target name as a prefix
392 *
393 * @return the guessed name
394 */
395 protected String getCurrentImageBestName(boolean prefix) {
396 if (prefix) {
397 return targetName + "_" + imageName;
398 }
399
400 return imageName;
401 }
402
403 /**
404 * Return the given word or sentence as <b>bold</b>.
405 *
406 * @param word
407 * the input
408 *
409 * @return the bold output
410 */
411 protected String enbold(String word) {
412 return word;
413 }
414
415 /**
416 * Return the given word or sentence as <i>italic</i>.
417 *
418 * @param word
419 * the input
420 *
421 * @return the italic output
422 */
423 protected String italize(String word) {
424 return word;
425 }
426
427 /**
428 * Decorate the given text with <b>bold</b> and <i>italic</i> words,
429 * according to {@link BasicOutput#enbold(String)} and
430 * {@link BasicOutput#italize(String)}.
431 *
432 * @param text
433 * the input
434 *
435 * @return the decorated output
436 */
437 protected String decorateText(String text) {
438 StringBuilder builder = new StringBuilder();
439
440 int bold = -1;
441 int italic = -1;
442 char prev = '\0';
443 for (char car : text.toCharArray()) {
444 switch (car) {
445 case '*':
446 if (bold >= 0 && prev != ' ') {
447 String data = builder.substring(bold);
448 builder.setLength(bold);
449 builder.append(enbold(data));
450 bold = -1;
451 } else if (bold < 0
452 && (prev == ' ' || prev == '\0' || prev == '\n')) {
453 bold = builder.length();
454 } else {
455 builder.append(car);
456 }
457
458 break;
459 case '_':
460 if (italic >= 0 && prev != ' ') {
461 String data = builder.substring(italic);
462 builder.setLength(italic);
463 builder.append(enbold(data));
464 italic = -1;
465 } else if (italic < 0
466 && (prev == ' ' || prev == '\0' || prev == '\n')) {
467 italic = builder.length();
468 } else {
469 builder.append(car);
470 }
471
472 break;
473 default:
474 builder.append(car);
475 break;
476 }
477
478 prev = car;
479 }
480
481 if (bold >= 0) {
482 builder.insert(bold, '*');
483 }
484
485 if (italic >= 0) {
486 builder.insert(italic, '_');
487 }
488
489 return builder.toString();
490 }
491
492 /**
493 * Return a {@link BasicOutput} object compatible with the given
494 * {@link OutputType}.
495 *
496 * @param type
497 * the type
498 * @param infoCover
499 * force the <tt>.info</tt> file and the cover to be saved next
500 * to the main target file
501 *
502 * @return the {@link BasicOutput}
503 */
504 public static BasicOutput getOutput(OutputType type, boolean infoCover) {
505 if (type != null) {
506 switch (type) {
507 case EPUB:
508 return new Epub().setType(type, infoCover, infoCover);
509 case TEXT:
510 return new Text().setType(type, true, infoCover);
511 case INFO_TEXT:
512 return new InfoText().setType(type, true, true);
513 case SYSOUT:
514 return new Sysout().setType(type, false, false);
515 case CBZ:
516 return new Cbz().setType(type, infoCover, infoCover);
517 case LATEX:
518 return new LaTeX().setType(type, infoCover, infoCover);
519 case HTML:
520 return new Html().setType(type, infoCover, infoCover);
521 }
522 }
523
524 return null;
525 }
526 }