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