Version 1.2.0: better UI, some fixes
[fanfix.git] / src / be / nikiroo / fanfix / 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 public String toString() {
49 return super.toString().toLowerCase();
50 }
51
52 /**
53 * A description of this output type.
54 *
55 * @return the description
56 */
57 public String getDesc() {
58 String desc = Instance.getTrans().getStringX(StringId.OUTPUT_DESC,
59 this.name());
60
61 if (desc == null) {
62 desc = Instance.getTrans()
63 .getString(StringId.OUTPUT_DESC, this);
64 }
65
66 return desc;
67 }
68
69 /**
70 * The default extension to add to the output files.
71 *
72 * @param readerTarget
73 * the target to point to to read the {@link Story} (for
74 * instance, the main entry point if this {@link Story} is in
75 * a directory bundle)
76 *
77 * @return the extension
78 */
79 public String getDefaultExtension(boolean readerTarget) {
80 BasicOutput output = BasicOutput.getOutput(this, false);
81 if (output != null) {
82 return output.getDefaultExtension(readerTarget);
83 }
84
85 return null;
86 }
87
88 /**
89 * Call {@link OutputType#valueOf(String.toUpperCase())}.
90 *
91 * @param typeName
92 * the possible type name
93 *
94 * @return NULL or the type
95 */
96 public static OutputType valueOfUC(String typeName) {
97 return OutputType.valueOf(typeName == null ? null : typeName
98 .toUpperCase());
99 }
100
101 /**
102 * Call {@link OutputType#valueOf(String.toUpperCase())} but return NULL
103 * for NULL and empty instead of raising an exception.
104 *
105 * @param typeName
106 * the possible type name
107 *
108 * @return NULL or the type
109 */
110 public static OutputType valueOfNullOkUC(String typeName) {
111 if (typeName == null || typeName.isEmpty()) {
112 return null;
113 }
114
115 return OutputType.valueOfUC(typeName);
116 }
117
118 /**
119 * Call {@link OutputType#valueOf(String.toUpperCase())} but return NULL
120 * in case of error instead of raising an exception.
121 *
122 * @param typeName
123 * the possible type name
124 *
125 * @return NULL or the type
126 */
127 public static OutputType valueOfAllOkUC(String typeName) {
128 try {
129 return OutputType.valueOfUC(typeName);
130 } catch (Exception e) {
131 return null;
132 }
133 }
134 }
135
136 /** The creator name (this program, by me!) */
137 static final String EPUB_CREATOR = "Fanfix (by Niki)";
138
139 /** The current best name for an image */
140 private String imageName;
141 private File targetDir;
142 private String targetName;
143 private OutputType type;
144 private boolean writeCover;
145 private boolean writeInfo;
146 private Progress storyPg;
147 private Progress chapPg;
148
149 /**
150 * Process the {@link Story} into the given target.
151 *
152 * @param story
153 * the {@link Story} to export
154 * @param target
155 * the target where to save to (will not necessary be taken as is
156 * by the processor, for instance an extension can be added)
157 * @param pg
158 * the optional progress reporter
159 *
160 * @return the actual main target saved, which can be slightly different
161 * that the input one
162 *
163 * @throws IOException
164 * in case of I/O error
165 */
166 public File process(Story story, String target, Progress pg)
167 throws IOException {
168 storyPg = pg;
169
170 target = new File(target).getAbsolutePath();
171 File targetDir = new File(target).getParentFile();
172 String targetName = new File(target).getName();
173
174 String ext = getDefaultExtension(false);
175 if (ext != null && !ext.isEmpty()) {
176 if (targetName.toLowerCase().endsWith(ext)) {
177 targetName = targetName.substring(0,
178 targetName.length() - ext.length());
179 }
180 }
181
182 return process(story, targetDir, targetName);
183 }
184
185 /**
186 * Process the {@link Story} into the given target.
187 * <p>
188 * This method is expected to be overridden in most cases.
189 *
190 * @param story
191 * the {@link Story} to export
192 * @param targetDir
193 * the target dir where to save to
194 * @param targetName
195 * the target filename (will not necessary be taken as is by the
196 * processor, for instance an extension can be added)
197 *
198 *
199 * @return the actual main target saved, which can be slightly different
200 * that the input one
201 *
202 * @throws IOException
203 * in case of I/O error
204 */
205 protected File process(Story story, File targetDir, String targetName)
206 throws IOException {
207 this.targetDir = targetDir;
208 this.targetName = targetName;
209
210 writeStory(story);
211
212 return null;
213 }
214
215 /**
216 * The output type.
217 *
218 * @return the type
219 */
220 public OutputType getType() {
221 return type;
222 }
223
224 /**
225 * The output type.
226 *
227 * @param type
228 * the new type
229 * @param infoCover
230 * TRUE to enable the creation of a .info file and a cover if
231 * possible
232 *
233 * @return this
234 */
235 protected BasicOutput setType(OutputType type, boolean writeCover,
236 boolean writeInfo) {
237 this.type = type;
238 this.writeCover = writeCover;
239 this.writeInfo = writeInfo;
240
241 return this;
242 }
243
244 /**
245 * The default extension to add to the output files.
246 *
247 * @param readerTarget
248 * the target to point to to read the {@link Story} (for
249 * instance, the main entry point if this {@link Story} is in a
250 * directory bundle)
251 *
252 * @return the extension
253 */
254 public String getDefaultExtension(boolean readerTarget) {
255 return "";
256 }
257
258 protected void writeStoryHeader(Story story) throws IOException {
259 }
260
261 protected void writeChapterHeader(Chapter chap) throws IOException {
262 }
263
264 protected void writeParagraphHeader(Paragraph para) throws IOException {
265 }
266
267 protected void writeStoryFooter(Story story) throws IOException {
268 }
269
270 protected void writeChapterFooter(Chapter chap) throws IOException {
271 }
272
273 protected void writeParagraphFooter(Paragraph para) throws IOException {
274 }
275
276 protected void writeStory(Story story) throws IOException {
277 if (storyPg == null) {
278 storyPg = new Progress(0, story.getChapters().size() + 2);
279 } else {
280 storyPg.setMinMax(0, story.getChapters().size() + 2);
281 }
282
283 String chapterNameNum = String.format("%03d", 0);
284 String paragraphNumber = String.format("%04d", 0);
285 imageName = paragraphNumber + "_" + chapterNameNum + ".png";
286
287 if (story.getMeta() != null) {
288 story.getMeta().setType("" + getType());
289 }
290
291 if (writeCover) {
292 InfoCover.writeCover(targetDir, targetName, story.getMeta());
293 }
294 if (writeInfo) {
295 InfoCover.writeInfo(targetDir, targetName, story.getMeta());
296 }
297
298 storyPg.setProgress(1);
299
300 List<Progress> chapPgs = new ArrayList<Progress>(story.getChapters()
301 .size());
302 for (Chapter chap : story) {
303 chapPg = new Progress(0, chap.getParagraphs().size());
304 storyPg.addProgress(chapPg, 1);
305 chapPgs.add(chapPg);
306 chapPg = null;
307 }
308
309 writeStoryHeader(story);
310 for (int i = 0; i < story.getChapters().size(); i++) {
311 chapPg = chapPgs.get(i);
312 writeChapter(story.getChapters().get(i));
313 chapPg.setProgress(chapPg.getMax());
314 chapPg = null;
315 }
316 writeStoryFooter(story);
317
318 storyPg.setProgress(storyPg.getMax());
319 storyPg = null;
320 }
321
322 protected void writeChapter(Chapter chap) throws IOException {
323 String chapterNameNum;
324 if (chap.getName() == null || chap.getName().isEmpty()) {
325 chapterNameNum = String.format("%03d", chap.getNumber());
326 } else {
327 chapterNameNum = String.format("%03d", chap.getNumber()) + "_"
328 + chap.getName().replace(" ", "_");
329 }
330
331 int num = 0;
332 String paragraphNumber = String.format("%04d", num++);
333 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
334
335 writeChapterHeader(chap);
336 int i = 1;
337 for (Paragraph para : chap) {
338 paragraphNumber = String.format("%04d", num++);
339 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
340 writeParagraph(para);
341 if (chapPg != null) {
342 chapPg.setProgress(i++);
343 }
344 }
345 writeChapterFooter(chap);
346 }
347
348 protected void writeParagraph(Paragraph para) throws IOException {
349 writeParagraphHeader(para);
350 writeTextLine(para.getType(), para.getContent());
351 writeParagraphFooter(para);
352 }
353
354 protected void writeTextLine(ParagraphType type, String line)
355 throws IOException {
356 }
357
358 /**
359 * Return the current best guess for an image name, based upon the current
360 * {@link Chapter} and {@link Paragraph}.
361 *
362 * @param prefix
363 * add the original target name as a prefix
364 *
365 * @return the guessed name
366 */
367 protected String getCurrentImageBestName(boolean prefix) {
368 if (prefix) {
369 return targetName + "_" + imageName;
370 }
371
372 return imageName;
373 }
374
375 /**
376 * Return the given word or sentence as <b>bold</b>.
377 *
378 * @param word
379 * the input
380 *
381 * @return the bold output
382 */
383 protected String enbold(String word) {
384 return word;
385 }
386
387 /**
388 * Return the given word or sentence as <i>italic</i>.
389 *
390 * @param word
391 * the input
392 *
393 * @return the italic output
394 */
395 protected String italize(String word) {
396 return word;
397 }
398
399 /**
400 * Decorate the given text with <b>bold</b> and <i>italic</i> words,
401 * according to {@link BasicOutput#enbold(String)} and
402 * {@link BasicOutput#italize(String)}.
403 *
404 * @param text
405 * the input
406 *
407 * @return the decorated output
408 */
409 protected String decorateText(String text) {
410 StringBuilder builder = new StringBuilder();
411
412 int bold = -1;
413 int italic = -1;
414 char prev = '\0';
415 for (char car : text.toCharArray()) {
416 switch (car) {
417 case '*':
418 if (bold >= 0 && prev != ' ') {
419 String data = builder.substring(bold);
420 builder.setLength(bold);
421 builder.append(enbold(data));
422 bold = -1;
423 } else if (bold < 0
424 && (prev == ' ' || prev == '\0' || prev == '\n')) {
425 bold = builder.length();
426 } else {
427 builder.append(car);
428 }
429
430 break;
431 case '_':
432 if (italic >= 0 && prev != ' ') {
433 String data = builder.substring(italic);
434 builder.setLength(italic);
435 builder.append(enbold(data));
436 italic = -1;
437 } else if (italic < 0
438 && (prev == ' ' || prev == '\0' || prev == '\n')) {
439 italic = builder.length();
440 } else {
441 builder.append(car);
442 }
443
444 break;
445 default:
446 builder.append(car);
447 break;
448 }
449
450 prev = car;
451 }
452
453 if (bold >= 0) {
454 builder.insert(bold, '*');
455 }
456
457 if (italic >= 0) {
458 builder.insert(italic, '_');
459 }
460
461 return builder.toString();
462 }
463
464 /**
465 * Return a {@link BasicOutput} object compatible with the given
466 * {@link OutputType}.
467 *
468 * @param type
469 * the type
470 * @param infoCover
471 * force the <tt>.info</tt> file and the cover to be saved next
472 * to the main target file
473 *
474 * @return the {@link BasicOutput}
475 */
476 public static BasicOutput getOutput(OutputType type, boolean infoCover) {
477 if (type != null) {
478 switch (type) {
479 case EPUB:
480 return new Epub().setType(type, infoCover, infoCover);
481 case TEXT:
482 return new Text().setType(type, true, infoCover);
483 case INFO_TEXT:
484 return new InfoText().setType(type, true, true);
485 case SYSOUT:
486 return new Sysout().setType(type, false, false);
487 case CBZ:
488 return new Cbz().setType(type, infoCover, infoCover);
489 case LATEX:
490 return new LaTeX().setType(type, infoCover, infoCover);
491 case HTML:
492 return new Html().setType(type, infoCover, infoCover);
493 }
494 }
495
496 return null;
497 }
498 }