1 package be
.nikiroo
.fanfix
.output
;
4 import java
.io
.IOException
;
5 import java
.util
.ArrayList
;
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 import be
.nikiroo
.utils
.Version
;
18 * This class is the base class used by the other output classes. It can be used
19 * outside of this package, and have static method that you can use to get
20 * access to the correct support class.
24 public abstract class BasicOutput
{
26 * The supported output types for which we can get a {@link BasicOutput}
31 public enum OutputType
{
32 /** EPUB files created with this program */
34 /** Pure text file with some rules */
36 /** TEXT but with associated .info file */
38 /** DEBUG output to console */
40 /** ZIP with (PNG) images */
42 /** LaTeX file with "book" template */
44 /** HTML files in a dedicated directory */
50 public String
toString() {
51 return super.toString().toLowerCase();
55 * A description of this output type.
58 * TRUE for the long description, FALSE for the short one
60 * @return the description
62 public String
getDesc(boolean longDesc
) {
63 StringId id
= longDesc ? StringId
.OUTPUT_DESC
64 : StringId
.OUTPUT_DESC_SHORT
;
66 String desc
= Instance
.getInstance().getTrans().getStringX(id
, this.name());
69 desc
= Instance
.getInstance().getTrans().getString(id
, this.toString());
72 if (desc
== null || desc
.isEmpty()) {
73 desc
= this.toString();
80 * The default extension to add to the output files.
83 * TRUE to point to the main {@link Story} entry point for a
84 * reader (for instance, the main entry point if this
85 * {@link Story} is in a directory bundle), FALSE to point to
86 * the main file even if it is a directory for instance
88 * @return the extension
90 public String
getDefaultExtension(boolean readerTarget
) {
91 BasicOutput output
= BasicOutput
.getOutput(this, false, false);
93 return output
.getDefaultExtension(readerTarget
);
100 * Call {@link OutputType#valueOf(String)} after conversion to upper
104 * the possible type name
106 * @return NULL or the type
108 public static OutputType
valueOfUC(String typeName
) {
109 return OutputType
.valueOf(typeName
== null ?
null : typeName
114 * Call {@link OutputType#valueOf(String)} after conversion to upper
115 * case but return def for NULL and empty instead of raising an
119 * the possible type name
123 * @return NULL or the type
125 public static OutputType
valueOfNullOkUC(String typeName
, OutputType def
) {
126 if (typeName
== null || typeName
.isEmpty()) {
130 return OutputType
.valueOfUC(typeName
);
134 * Call {@link OutputType#valueOf(String)} after conversion to upper
135 * case but return def in case of error instead of raising an exception.
138 * the possible type name
142 * @return NULL or the type
144 public static OutputType
valueOfAllOkUC(String typeName
, OutputType def
) {
146 return OutputType
.valueOfUC(typeName
);
147 } catch (Exception e
) {
153 /** The creator name (this program, by me!) */
154 static protected final String EPUB_CREATOR
= "Fanfix "
155 + Version
.getCurrentVersion() + " (by Niki)";
157 /** The current best name for an image */
158 private String imageName
;
159 private File targetDir
;
160 private String targetName
;
161 private OutputType type
;
162 private boolean writeCover
;
163 private boolean writeInfo
;
164 private Progress storyPg
;
165 private Progress chapPg
;
168 * Process the {@link Story} into the given target.
171 * the {@link Story} to export
173 * the target where to save to (will not necessary be taken as is
174 * by the processor, for instance an extension can be added)
176 * the optional progress reporter
178 * @return the actual main target saved, which can be slightly different
181 * @throws IOException
182 * in case of I/O error
184 public File
process(Story story
, String target
, Progress pg
)
188 File targetDir
= null;
189 String targetName
= null;
190 if (target
!= null) {
191 target
= new File(target
).getAbsolutePath();
192 targetDir
= new File(target
).getParentFile();
193 targetName
= new File(target
).getName();
195 String ext
= getDefaultExtension(false);
196 if (ext
!= null && !ext
.isEmpty()) {
197 if (targetName
.toLowerCase().endsWith(ext
)) {
198 targetName
= targetName
.substring(0, targetName
.length()
204 return process(story
, targetDir
, targetName
);
208 * Process the {@link Story} into the given target.
210 * This method is expected to be overridden in most cases.
213 * the {@link Story} to export
215 * the target dir where to save to
217 * the target filename (will not necessary be taken as is by the
218 * processor, for instance an extension can be added)
221 * @return the actual main target saved, which can be slightly different
224 * @throws IOException
225 * in case of I/O error
227 protected File
process(Story story
, File targetDir
, String targetName
)
229 this.targetDir
= targetDir
;
230 this.targetName
= targetName
;
242 public OutputType
getType() {
247 * Enable the creation of a .info file next to the resulting processed file.
249 * @return TRUE to enable it
251 protected boolean isWriteInfo() {
256 * Enable the creation of a cover file next to the resulting processed file
259 * @return TRUE to enable it
261 protected boolean isWriteCover() {
271 * TRUE to enable the creation of a cover if possible
273 * TRUE to enable the creation of a .info file
277 protected BasicOutput
setType(OutputType type
, boolean writeInfo
,
278 boolean writeCover
) {
280 this.writeInfo
= writeInfo
;
281 this.writeCover
= writeCover
;
287 * The default extension to add to the output files.
289 * @param readerTarget
290 * TRUE to point to the main {@link Story} entry point for a
291 * reader (for instance, the main entry point if this
292 * {@link Story} is in a directory bundle), FALSE to point to the
293 * main file even if it is a directory for instance
295 * @return the extension
297 public String
getDefaultExtension(
298 @SuppressWarnings("unused") boolean readerTarget
) {
302 @SuppressWarnings("unused")
303 protected void writeStoryHeader(Story story
) throws IOException
{
306 @SuppressWarnings("unused")
307 protected void writeChapterHeader(Chapter chap
) throws IOException
{
310 @SuppressWarnings("unused")
311 protected void writeParagraphHeader(Paragraph para
) throws IOException
{
314 @SuppressWarnings("unused")
315 protected void writeStoryFooter(Story story
) throws IOException
{
318 @SuppressWarnings("unused")
319 protected void writeChapterFooter(Chapter chap
) throws IOException
{
322 @SuppressWarnings("unused")
323 protected void writeParagraphFooter(Paragraph para
) throws IOException
{
326 protected void writeStory(Story story
) throws IOException
{
327 if (storyPg
== null) {
328 storyPg
= new Progress(0, story
.getChapters().size() + 2);
330 storyPg
.setMinMax(0, story
.getChapters().size() + 2);
333 String chapterNameNum
= String
.format("%03d", 0);
334 String paragraphNumber
= String
.format("%04d", 0);
335 imageName
= paragraphNumber
+ "_" + chapterNameNum
;
337 if (story
.getMeta() != null) {
338 story
.getMeta().setType("" + getType());
341 if (isWriteCover()) {
342 InfoCover
.writeCover(targetDir
, targetName
, story
.getMeta());
345 InfoCover
.writeInfo(targetDir
, targetName
, story
.getMeta());
348 storyPg
.setProgress(1);
350 List
<Progress
> chapPgs
= new ArrayList
<Progress
>(story
.getChapters()
352 for (Chapter chap
: story
) {
353 chapPg
= new Progress(0, chap
.getParagraphs().size());
354 storyPg
.addProgress(chapPg
, 1);
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());
366 writeStoryFooter(story
);
368 storyPg
.setProgress(storyPg
.getMax());
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());
377 chapterNameNum
= String
.format("%03d", chap
.getNumber()) + "_"
378 + chap
.getName().replace(" ", "_");
382 String paragraphNumber
= String
.format("%04d", num
++);
383 imageName
= chapterNameNum
+ "_" + paragraphNumber
;
385 writeChapterHeader(chap
);
387 for (Paragraph para
: chap
) {
388 paragraphNumber
= String
.format("%04d", num
++);
389 imageName
= chapterNameNum
+ "_" + paragraphNumber
;
390 writeParagraph(para
);
391 if (chapPg
!= null) {
392 chapPg
.setProgress(i
++);
395 writeChapterFooter(chap
);
398 protected void writeParagraph(Paragraph para
) throws IOException
{
399 writeParagraphHeader(para
);
400 writeTextLine(para
.getType(), para
.getContent());
401 writeParagraphFooter(para
);
404 @SuppressWarnings("unused")
405 protected void writeTextLine(ParagraphType type
, String line
)
410 * Return the current best guess for an image name, based upon the current
411 * {@link Chapter} and {@link Paragraph}.
414 * add the original target name as a prefix
416 * @return the guessed name
418 protected String
getCurrentImageBestName(boolean prefix
) {
420 return targetName
+ "_" + imageName
;
427 * Return the given word or sentence as <b>bold</b>.
432 * @return the bold output
434 protected String
enbold(String word
) {
439 * Return the given word or sentence as <i>italic</i>.
444 * @return the italic output
446 protected String
italize(String word
) {
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)}.
458 * @return the decorated output
460 protected String
decorateText(String text
) {
461 StringBuilder builder
= new StringBuilder();
466 for (char car
: text
.toCharArray()) {
469 if (bold
>= 0 && prev
!= ' ') {
470 String data
= builder
.substring(bold
);
471 builder
.setLength(bold
);
472 builder
.append(enbold(data
));
475 && (prev
== ' ' || prev
== '\0' || prev
== '\n')) {
476 bold
= builder
.length();
483 if (italic
>= 0 && prev
!= ' ') {
484 String data
= builder
.substring(italic
);
485 builder
.setLength(italic
);
486 builder
.append(enbold(data
));
488 } else if (italic
< 0
489 && (prev
== ' ' || prev
== '\0' || prev
== '\n')) {
490 italic
= builder
.length();
505 builder
.insert(bold
, '*');
509 builder
.insert(italic
, '_');
512 return builder
.toString();
516 * Return a {@link BasicOutput} object compatible with the given
517 * {@link OutputType}.
522 * TRUE to enable the creation of a cover if possible to be saved
523 * next to the main target file
525 * TRUE to enable the creation of a .info file to be saved next
526 * to the main target file
528 * @return the {@link BasicOutput}
530 public static BasicOutput
getOutput(OutputType type
, boolean writeInfo
,
531 boolean writeCover
) {
535 return new Epub().setType(type
, writeInfo
, writeCover
);
537 return new Text().setType(type
, writeInfo
, true);
539 return new InfoText().setType(type
, true, true);
541 return new Sysout().setType(type
, false, false);
543 return new Cbz().setType(type
, writeInfo
, writeCover
);
545 return new LaTeX().setType(type
, writeInfo
, writeCover
);
547 return new Html().setType(type
, writeInfo
, writeCover
);