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