1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.gnu.jfiglet.core;
22
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.net.URL;
28 import java.util.NoSuchElementException;
29 import java.util.StringTokenizer;
30 import java.util.zip.ZipEntry;
31 import java.util.zip.ZipException;
32 import java.util.zip.ZipInputStream;
33
34 /***
35 * A FIGfont, which file format is specified by
36 * <A HREF="doc-files/figfont.txt">this document</A>.
37 *
38 * @TODO : READ THE EXTRA NON ASCII CHARACTERS IN FIGFONT FILES
39 *
40 * @version $Id: FIGFont.java,v 1.3 2004/04/27 20:09:28 sbrunot Exp $
41 *
42 * @author <a href="mailto:sebastien.brunot@club-internet.fr">
43 * Sebastien Brunot</a>
44 */
45 public class FIGFont
46 {
47
48
49
50
51 /***
52 * The maximum valid code for a character of the FIGFont
53 * 8-bit characterset
54 */
55 public static final int MAX_8BIT_CHARSET_CHAR_CODE = 255;
56
57 /***
58 * The separator between tokens in a FIGFont file header
59 */
60 private static final String FIG_FONT_HEADER_TOKEN_SEPARATOR = " ";
61
62 /***
63 * The signature of a FIGFont file (the 5 first characters of the
64 * (unziped) file)
65 */
66 private static final String FIGFONT_FILE_SIGNATURE = "flf2a";
67
68 /***
69 * The extension of a FIGFont file (which name is FIGFontName.extension)
70 */
71 private static final String FIGFONT_EXTENSION = "flf";
72
73 /***
74 * The name of the fonts directory in the classpath
75 */
76 private static final String FONT_DIRECTORY_NAME = "fonts";
77
78
79
80
81
82 /***
83 * Informations about the FIGFont
84 */
85 private FIGFontInfo info = null;
86
87 /***
88 * An array that holds the FIGCharacters of the font. The index
89 * of a FIGCharacter in this array is the code of the
90 * subcharacter it stands for in its characterset.
91 */
92 private FIGCharacter[] eightBitCharacterSet =
93 new FIGCharacter[MAX_8BIT_CHARSET_CHAR_CODE + 1];
94
95
96
97
98
99 /***
100 * Create an empty FIGFont whith a provided name.
101 * @param theName the name of the font
102 */
103 public FIGFont(String theName)
104 {
105 this.info = new FIGFontInfo();
106 this.info.setName(theName);
107 }
108
109
110
111
112
113 /***
114 * Load the content of the FIGFont from the <em>fontname</em>.flf file
115 * provided in the fonts directory in the classpath.
116 * @throws IOException if such an exception occured while accessing the
117 * <em>fontname</em>.flf file.
118 * @throws ZipException if the <em>fontname</em>.flf file is a zip file
119 * and such an exception occurs reading its content.
120 * @throws IllegalFIGFontFileException if the <em>fontname</em>.flf file
121 * provided in the classpath is not a valid FIGFont file or if there is
122 * no <em>fontname</em>.flf file provided in the fonts directory in the
123 * classpath.
124 */
125 public void loadFromFile()
126 throws IOException, ZipException, IllegalFIGFontFileException
127 {
128
129 String fontFilePath =
130 FONT_DIRECTORY_NAME
131 + "/"
132 + this.info.getName()
133 + "."
134 + FIGFONT_EXTENSION;
135
136 URL fontFileURL = ClassLoader.getSystemResource(fontFilePath);
137 if (fontFileURL == null)
138 {
139
140 throw new IllegalFIGFontFileException(
141 "Cannot find " + fontFilePath + " in the classpath.");
142 }
143
144 InputStream fontFileInputStream = fontFileURL.openStream();
145
146
147 ZipInputStream fontFileZipInputStream =
148 new ZipInputStream(fontFileInputStream);
149
150
151 ZipEntry entry = fontFileZipInputStream.getNextEntry();
152 if (entry == null)
153 {
154
155
156
157
158 fontFileZipInputStream.close();
159 fontFileInputStream.close();
160 fontFileInputStream = fontFileURL.openStream();
161 }
162 else
163 {
164
165
166 fontFileInputStream = fontFileZipInputStream;
167 }
168 readFontFile(fontFileInputStream);
169 fontFileInputStream.close();
170 }
171
172 /***
173 * Get one of the FIGFont FIGCharacters.
174 * @param theCharacterCode the code of the FIGCharacter in
175 * the FIGFont characterset. An IllegalArgumentException is thrown if
176 * this code is lower than 0 or greater than MAX_8BIT_CHARSET_CHAR_CODE
177 * @return the FIGCharacter which code is theCharacterCode in
178 * the FIGFont characterset. If no FIGCharacter is defined for this code
179 * in the FIGFont, the FIGCharacter which code is 0 is returned.
180 */
181 public FIGCharacter getFIGCharacter(int theCharacterCode)
182 {
183 if (theCharacterCode < 0
184 || theCharacterCode > MAX_8BIT_CHARSET_CHAR_CODE)
185 {
186 throw new IllegalArgumentException(
187 "The character code is an integer between 0 and "
188 + MAX_8BIT_CHARSET_CHAR_CODE
189 + " included");
190 }
191 FIGCharacter returnValue = this.eightBitCharacterSet[theCharacterCode];
192 if (returnValue == null)
193 {
194 returnValue = this.eightBitCharacterSet[0];
195 }
196 return this.eightBitCharacterSet[theCharacterCode];
197 }
198
199 /***
200 * Get informations about the FIGFont.
201 * @return informations about the FIGFont.
202 */
203 public FIGFontInfo getInfo()
204 {
205 return this.info;
206 }
207
208
209
210
211
212 /***
213 * Read the content of a .flf file and initialize the FIGFont
214 * with it.
215 * @param theFontFile an input stream to the content of the
216 * .flf file (or to the content of it first (unziped) entry
217 * if the .flf file is a zip file).
218 * @throws IOException if such an exception occurs reading
219 * theFontFile.
220 * @throws IllegalFIGFontFileException if theFontFile does
221 * not provide the content of a correct .flf file (see
222 * <A HREF="http://www.figlet.org">http://www.figlet.org</A>
223 * for .flf file specification.
224 */
225 private void readFontFile(InputStream theFontFile)
226 throws IOException, IllegalFIGFontFileException
227 {
228
229 BufferedReader fontFileReader =
230 new BufferedReader(new InputStreamReader(theFontFile));
231
232 String fontFileHeaderLine = fontFileReader.readLine();
233 try
234 {
235
236 FIGFontInfo infosFromTheHeader =
237 populateFIGFontInfoFromFIGFontFileHeader(fontFileHeaderLine);
238
239 infosFromTheHeader.setName(this.info.getName());
240
241 this.info = infosFromTheHeader;
242 }
243 catch (IllegalArgumentException e)
244 {
245
246 throw new IllegalFIGFontFileException(e.getMessage());
247 }
248
249
250 for (int i = 0; i < this.info.getCommentLinesNumber(); i++)
251 {
252 this.info.addCommentLine(fontFileReader.readLine());
253 }
254
255 for (int currentCharCode = 32;
256 currentCharCode < 127;
257 currentCharCode++)
258 {
259 String[] characterLines = new String[this.info.getHeight()];
260 for (int i = 0; i < this.info.getHeight(); i++)
261 {
262 characterLines[i] = fontFileReader.readLine();
263 }
264 this.eightBitCharacterSet[currentCharCode] =
265 new FIGCharacter(characterLines);
266 }
267
268 int[] deutchCharactersCodes = {196, 214, 220, 228, 246, 252, 223};
269 for (int i = 0; i < deutchCharactersCodes.length; i++)
270 {
271 String[] characterLines = new String[this.info.getHeight()];
272 for (int j = 0; j < this.info.getHeight(); j++)
273 {
274 characterLines[j] = fontFileReader.readLine();
275 }
276 this.eightBitCharacterSet[deutchCharactersCodes[i]] =
277 new FIGCharacter(characterLines);
278 }
279
280 boolean readNextCharacterDefinition = true;
281 while (readNextCharacterDefinition)
282 {
283
284 String characterHeader = fontFileReader.readLine();
285 if (characterHeader == null)
286 {
287
288 readNextCharacterDefinition = false;
289 break;
290 }
291 else
292 {
293 StringTokenizer tokenizer =
294 new StringTokenizer(characterHeader);
295
296
297 String characterCodeAsString = tokenizer.nextToken(" ");
298 try
299 {
300 int characterCode =
301 decodeCharacterCode(characterCodeAsString);
302
303 String[] characterLines = new String[this.info.getHeight()];
304 for (int i = 0; i < this.info.getHeight(); i++)
305 {
306 characterLines[i] = fontFileReader.readLine();
307 if (characterLines[i] == null)
308 {
309 throw new IllegalFIGFontFileException(
310 "Character which code is "
311 + characterCode
312 + " is not entirely defined");
313 }
314 }
315 if ((characterCode > 0)
316 && (characterCode < MAX_8BIT_CHARSET_CHAR_CODE))
317 {
318
319
320 this.eightBitCharacterSet[characterCode] =
321 new FIGCharacter(characterLines);
322 }
323 else
324 {
325
326
327 }
328 }
329 catch (NumberFormatException e)
330 {
331 throw new IllegalFIGFontFileException(
332 "Line \"" + characterHeader + " is uncorrect");
333 }
334 }
335 }
336 }
337
338 /***
339 * Returns a FIGFontInfo object populated with informations readen from
340 * the header line of a .flf file.
341 * @param theFIGFontFileHeader the header line to get font informations
342 * from. An IllegalArgumentException is raised if this is not a legal
343 * font header line, with a message describing the error.
344 * @return a FIGFontInfo object populated with informations readen from
345 * theFIGFontFileHeader.
346 */
347 private FIGFontInfo populateFIGFontInfoFromFIGFontFileHeader(
348 String theFIGFontFileHeader)
349 {
350 FIGFontInfo informations = new FIGFontInfo();
351
352 if (theFIGFontFileHeader == null)
353 {
354 throw new IllegalArgumentException("The header is null");
355 }
356
357
358 int oldLayout = -10;
359
360 Integer fullLayout = null;
361
362 StringTokenizer tokenizer =
363 new StringTokenizer(
364 theFIGFontFileHeader,
365 FIG_FONT_HEADER_TOKEN_SEPARATOR);
366
367 int numberOfTokens = tokenizer.countTokens();
368
369
370 if (numberOfTokens < 6)
371 {
372 throw new IllegalArgumentException(
373 "Not enough tokens in the header");
374 }
375 else if (numberOfTokens > 9)
376 {
377 throw new IllegalArgumentException("Too many tokens in the header");
378 }
379
380 String signatureAndHardblank = tokenizer.nextToken();
381 if (!(FIGFONT_FILE_SIGNATURE
382 .equals(
383 signatureAndHardblank.substring(
384 0,
385 FIGFONT_FILE_SIGNATURE.length()))))
386 {
387 throw new IllegalArgumentException("Uncorrect signature");
388 }
389
390 informations.setHardblank(
391 signatureAndHardblank.charAt(FIGFONT_FILE_SIGNATURE.length()));
392
393 try
394 {
395 informations.setHeight(Integer.parseInt(tokenizer.nextToken()));
396 }
397 catch (NumberFormatException e)
398 {
399 throw new IllegalArgumentException(
400 "Font height must be an integer");
401 }
402
403 try
404 {
405 informations.setBaseline(Integer.parseInt(tokenizer.nextToken()));
406 }
407 catch (NumberFormatException e)
408 {
409 throw new IllegalArgumentException(
410 "Font baseline must be an integer");
411 }
412
413 try
414 {
415 informations.setMaxLength(Integer.parseInt(tokenizer.nextToken()));
416 }
417 catch (NumberFormatException e)
418 {
419 throw new IllegalArgumentException(
420 "Font max length must be an integer");
421 }
422
423 try
424 {
425 oldLayout = Integer.parseInt(tokenizer.nextToken());
426 }
427 catch (NumberFormatException e)
428 {
429 throw new IllegalArgumentException(
430 "Old layout must be an integer");
431 }
432
433 try
434 {
435 informations.setCommentLinesNumber(
436 Integer.parseInt(tokenizer.nextToken()));
437 }
438 catch (NumberFormatException e)
439 {
440 throw new IllegalArgumentException(
441 "The number of lines of comments must be an integer");
442 }
443 try
444 {
445 try
446 {
447
448 Integer printDirection = new Integer(tokenizer.nextToken());
449 if (printDirection != null)
450 {
451
452 if ((printDirection.intValue()
453 != FIGFontInfo.PRINT_DIRECTION_LEFT_TO_RIGHT)
454 && (printDirection.intValue()
455 != FIGFontInfo.PRINT_DIRECTION_RIGHT_TO_LEFT))
456 {
457 throw new IllegalArgumentException(
458 "Accepted values for printDirection are "
459 + FIGFontInfo.PRINT_DIRECTION_LEFT_TO_RIGHT
460 + " for LEFT_TO_RIGHT and "
461 + FIGFontInfo.PRINT_DIRECTION_RIGHT_TO_LEFT
462 + " for RIGHT_TO_LEFT");
463 }
464
465 informations.setPrintDirection(printDirection.intValue());
466 }
467
468 try
469 {
470 fullLayout = new Integer(tokenizer.nextToken());
471 }
472 catch (NumberFormatException e)
473 {
474 throw new IllegalArgumentException(
475 "The Full Layout must be an integer");
476 }
477
478
479 }
480 catch (NumberFormatException e)
481 {
482 throw new IllegalArgumentException(
483 "The print direction must be an integer");
484 }
485 }
486 catch (NoSuchElementException e)
487 {
488
489 }
490
491 informations.setLayout(new FIGFontLayout(oldLayout, fullLayout));
492
493 return informations;
494 }
495
496 /***
497 * Decode the code of an extra character.
498 * A character code may be expressed in decimal (as shown above, numbers
499 * we're all familiar with), or in Octal (seldom used) or in hexadecimal.
500 * Character codes expressed in octal must be preceded by "0" (zero), and if
501 * negative, "-" (minus) must precede the "0". There are eight octal
502 * digits: 01234567. You may recall octal numbers from school as "base 8
503 * numbers".
504 * Character codes expressed in hexadecimal must be preceded by "0x" or
505 * "0X" (That's also a zero.) If negative, the "-" must precede the "0x".
506 * There are 16 hexadecimal digits: 01234567890ABCDEF. (The "letter-digits"
507 * may also be lowercase.) Hexadecimal is "base 16".
508 * @param theCharacterCodeAsString the code of the character as expressed in
509 * the FIGFont file
510 * @return the code of the character as an int.
511 * @throws NumberFormatException if theCharacterCodeAsString is not
512 * an integer in one of the recognized formats.
513 */
514 private int decodeCharacterCode(String theCharacterCodeAsString)
515 {
516 String codeAsString = new String(theCharacterCodeAsString);
517
518 int sign = 1;
519
520 int characterCodeRadix = 10;
521 if (codeAsString.charAt(0) == '-')
522 {
523
524 sign = -1;
525 codeAsString = codeAsString.substring(1);
526 }
527 if (codeAsString.charAt(0) == '0')
528 {
529
530 characterCodeRadix = 8;
531 codeAsString = codeAsString.substring(1);
532 if ((codeAsString.charAt(0) == 'X')
533 || (codeAsString.charAt(0) == 'x'))
534 {
535
536 characterCodeRadix = 16;
537 codeAsString = codeAsString.substring(1);
538 }
539 }
540 return (sign * Integer.parseInt(codeAsString, characterCodeRadix));
541 }
542
543 }