Branch libreoffice-5-0-4
[LibreOffice.git] / vcl / generic / fontmanager / parseAFM.cxx
blob91c7a895c9612a528cfe9a5658d84f63f6a6ac2e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * (C) 1988, 1989, 1990 by Adobe Systems Incorporated. All rights reserved.
5 * This file may be freely copied and redistributed as long as:
6 * 1) This entire notice continues to be included in the file,
7 * 2) If the file has been modified in any way, a notice of such
8 * modification is conspicuously indicated.
10 * PostScript, Display PostScript, and Adobe are registered trademarks of
11 * Adobe Systems Incorporated.
13 * ************************************************************************
14 * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT
15 * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS
16 * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR
17 * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY
18 * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION,
19 * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
21 * ************************************************************************
25 * Changes made for OpenOffice.org
27 * 10/24/2000 pl - changed code to compile with c++-compilers
28 * - added namespace to avoid symbol clashes
29 * - replaced BOOL by bool
30 * - added function to free space allocated by parseFile
31 * 10/26/2000 pl - added additional keys
32 * - added ability to parse slightly broken files
33 * - added charwidth member to GlobalFontInfo
34 * 04/26/2001 pl - added OpenOffice header
35 * 10/19/2005 pl - performance increase:
36 * - fread file in one pass
37 * - replace file io by buffer access
38 * 10/20/2005 pl - performance increase:
39 * - use one table lookup in token() routine
40 * instead of many conditions
41 * - return token length in token() routine
42 * - use hash lookup instead of binary search
43 * in recognize() routine
46 /* parseAFM.c
48 * This file is used in conjunction with the parseAFM.h header file.
49 * This file contains several procedures that are used to parse AFM
50 * files. It is intended to work with an application program that needs
51 * font metric information. The program can be used as is by making a
52 * procedure call to "parseFile" (passing in the expected parameters)
53 * and having it fill in a data structure with the data from the
54 * AFM file, or an application developer may wish to customize this
55 * code.
57 * There is also a file, parseAFMclient.c, that is a sample application
58 * showing how to call the "parseFile" procedure and how to use the data
59 * after "parseFile" has returned.
61 * Please read the comments in parseAFM.h and parseAFMclient.c.
63 * History:
64 * original: DSM Thu Oct 20 17:39:59 PDT 1988
65 * modified: DSM Mon Jul 3 14:17:50 PDT 1989
66 * - added 'storageProblem' return code
67 * - fixed bug of not allocating extra byte for string duplication
68 * - fixed typos
69 * modified: DSM Tue Apr 3 11:18:34 PDT 1990
70 * - added free(ident) at end of parseFile routine
71 * modified: DSM Tue Jun 19 10:16:29 PDT 1990
72 * - changed (width == 250) to (width = 250) in initializeArray
75 #include <stdio.h>
76 #include <string.h>
77 #include <stdlib.h>
78 #include <sys/stat.h>
80 #include "parseAFM.hxx"
81 #include "vcl/strhelper.hxx"
83 #include "rtl/alloc.h"
85 #define lineterm EOL /* line terminating character */
86 #define normalEOF 1 /* return code from parsing routines used only */
87 /* in this module */
88 #define False "false" /* used in string comparison to check the value of */
89 /* boolean keys (e.g. IsFixedPitch) */
91 #define MATCH(A,B) (strncmp((A),(B), MAX_NAME) == 0)
93 namespace psp {
95 class FileInputStream
97 char* m_pMemory;
98 unsigned int m_nPos;
99 unsigned int m_nLen;
100 public:
101 FileInputStream( const char* pFilename );
102 ~FileInputStream();
104 int getChar() { return (m_nPos < m_nLen) ? int(m_pMemory[m_nPos++]) : -1; }
105 void ungetChar()
107 if( m_nPos > 0 )
108 m_nPos--;
112 FileInputStream::FileInputStream(const char* pFilename)
113 : m_pMemory(NULL)
114 , m_nPos(0)
115 , m_nLen(0)
117 FILE* fp = fopen( pFilename, "r" );
118 if( fp )
120 struct stat aStat;
121 if (!fstat(fileno(fp), &aStat) && S_ISREG(aStat.st_mode) && aStat.st_size > 0)
123 m_pMemory = static_cast<char*>(rtl_allocateMemory( aStat.st_size ));
124 m_nLen = (unsigned int)fread( m_pMemory, 1, aStat.st_size, fp );
126 fclose( fp );
130 FileInputStream::~FileInputStream()
132 rtl_freeMemory( m_pMemory );
135 /*************************** GLOBALS ***********************/
136 /* "shorts" for fast case statement
137 * The values of each of these enumerated items correspond to an entry in the
138 * table of strings defined below. Therefore, if you add a new string as
139 * new keyword into the keyStrings table, you must also add a corresponding
140 * parseKey AND it MUST be in the same position!
142 * IMPORTANT: since the sorting algorithm is a binary search, the strings of
143 * keywords must be placed in lexicographical order, below. [Therefore, the
144 * enumerated items are not necessarily in lexicographical order, depending
145 * on the name chosen. BUT, they must be placed in the same position as the
146 * corresponding key string.] The NOPE shall remain in the last position,
147 * since it does not correspond to any key string, and it is used in the
148 * "recognize" procedure to calculate how many possible keys there are.
151 // some metrics have Ascent, Descent instead Ascender, Descender or Em
152 // which is not allowed per afm spcification, but let us handle
153 // this gently
154 enum parseKey {
155 ASCENDER, ASCENT, CHARBBOX, CODE, COMPCHAR, CODEHEX, CAPHEIGHT, CHARWIDTH, CHARACTERSET, CHARACTERS, COMMENT,
156 DESCENDER, DESCENT, EM, ENCODINGSCHEME, ENDCHARMETRICS, ENDCOMPOSITES, ENDDIRECTION,
157 ENDFONTMETRICS, ENDKERNDATA, ENDKERNPAIRS, ENDTRACKKERN,
158 FAMILYNAME, FONTBBOX, FONTNAME, FULLNAME, ISBASEFONT, ISFIXEDPITCH,
159 ITALICANGLE, KERNPAIR, KERNPAIRXAMT, LIGATURE, MAPPINGSCHEME, METRICSSETS, CHARNAME,
160 NOTICE, COMPCHARPIECE, STARTCHARMETRICS, STARTCOMPOSITES, STARTDIRECTION,
161 STARTFONTMETRICS, STARTKERNDATA, STARTKERNPAIRS,
162 STARTTRACKKERN, STDHW, STDVW, TRACKKERN, UNDERLINEPOSITION,
163 UNDERLINETHICKNESS, VVECTOR, VERSION, XYWIDTH, X0WIDTH, XWIDTH, WEIGHT, XHEIGHT,
164 NOPE
167 /*************************** PARSING ROUTINES **************/
169 /*************************** token *************************/
171 /* A "AFM file Conventions" tokenizer. That means that it will
172 * return the next token delimited by white space. See also
173 * the `linetoken' routine, which does a similar thing but
174 * reads all tokens until the next end-of-line.
177 // token white space is ' ', '\n', '\r', ',', '\t', ';'
178 static const bool is_white_Array[ 256 ] =
179 { false, false, false, false, false, false, false, false, // 0-7
180 false, true, true, false, false, true, false, false, // 8-15
181 false, false, false, false, false, false, false, false, // 16-23
182 false, false, false, false, false, false, false, false, // 24-31
183 true, false, false, false, false, false, false, false, // 32-39
184 false, false, false, false, true, false, false, false, // 40-47
185 false, false, false, false, false, false, false, false, // 48-55
186 false, false, false, true, false, false, false, false, // 56-63
188 false, false, false, false, false, false, false, false, // 64 -
189 false, false, false, false, false, false, false, false,
190 false, false, false, false, false, false, false, false,
191 false, false, false, false, false, false, false, false,
192 false, false, false, false, false, false, false, false,
193 false, false, false, false, false, false, false, false,
194 false, false, false, false, false, false, false, false,
195 false, false, false, false, false, false, false, false, // 127
197 false, false, false, false, false, false, false, false, // 128 -
198 false, false, false, false, false, false, false, false,
199 false, false, false, false, false, false, false, false,
200 false, false, false, false, false, false, false, false,
201 false, false, false, false, false, false, false, false,
202 false, false, false, false, false, false, false, false,
203 false, false, false, false, false, false, false, false,
204 false, false, false, false, false, false, false, false, // 191
206 false, false, false, false, false, false, false, false, // 192 -
207 false, false, false, false, false, false, false, false,
208 false, false, false, false, false, false, false, false,
209 false, false, false, false, false, false, false, false,
210 false, false, false, false, false, false, false, false,
211 false, false, false, false, false, false, false, false,
212 false, false, false, false, false, false, false, false,
213 false, false, false, false, false, false, false, false, // 255
215 // token delimiters are ' ', '\n', '\r', '\t', ':', ';'
216 static const bool is_delimiter_Array[ 256 ] =
217 { false, false, false, false, false, false, false, false, // 0-7
218 false, true, true, false, false, true, false, false, // 8-15
219 false, false, false, false, false, false, false, false, // 16-23
220 false, false, false, false, false, false, false, false, // 24-31
221 true, false, false, false, false, false, false, false, // 32-39
222 false, false, false, false, false, false, false, false, // 40-47
223 false, false, false, false, false, false, false, false, // 48-55
224 false, false, true, true, false, false, false, false, // 56-63
226 false, false, false, false, false, false, false, false, // 64 -
227 false, false, false, false, false, false, false, false,
228 false, false, false, false, false, false, false, false,
229 false, false, false, false, false, false, false, false,
230 false, false, false, false, false, false, false, false,
231 false, false, false, false, false, false, false, false,
232 false, false, false, false, false, false, false, false,
233 false, false, false, false, false, false, false, false, // 127
235 false, false, false, false, false, false, false, false, // 128 -
236 false, false, false, false, false, false, false, false,
237 false, false, false, false, false, false, false, false,
238 false, false, false, false, false, false, false, false,
239 false, false, false, false, false, false, false, false,
240 false, false, false, false, false, false, false, false,
241 false, false, false, false, false, false, false, false,
242 false, false, false, false, false, false, false, false, // 191
244 false, false, false, false, false, false, false, false, // 192 -
245 false, false, false, false, false, false, false, false,
246 false, false, false, false, false, false, false, false,
247 false, false, false, false, false, false, false, false,
248 false, false, false, false, false, false, false, false,
249 false, false, false, false, false, false, false, false,
250 false, false, false, false, false, false, false, false,
251 false, false, false, false, false, false, false, false, // 255
253 static char *token( FileInputStream* stream, int& rLen )
255 static char ident[MAX_NAME]; /* storage buffer for keywords */
257 int ch, idx;
259 /* skip over white space */
260 // relies on EOF = -1
261 while( is_white_Array[ (ch = stream->getChar()) & 255 ] )
264 idx = 0;
265 while( ch != -1 && ! is_delimiter_Array[ ch & 255 ] && idx < MAX_NAME-1 )
267 ident[idx++] = ch;
268 ch = stream->getChar();
271 if (ch == -1 && idx < 1) return ((char *)NULL);
272 if (idx >= 1 && ch != ':' && ch != -1) stream->ungetChar();
273 if (idx < 1 ) ident[idx++] = ch; /* single-character token */
274 ident[idx] = 0;
275 rLen = idx;
277 return ident; /* returns pointer to the token */
279 } /* token */
281 /*************************** linetoken *************************/
283 /* "linetoken" will get read all tokens until the EOL character from
284 * the given stream. This is used to get any arguments that can be
285 * more than one word (like Comment lines and FullName).
288 static char *linetoken( FileInputStream* stream )
290 static char ident[MAX_NAME]; /* storage buffer for keywords */
291 int ch, idx;
293 while ((ch = stream->getChar()) == ' ' || ch == '\t' ) ;
295 idx = 0;
296 while (ch != -1 && ch != lineterm && ch != '\r' && idx < MAX_NAME-1 )
298 ident[idx++] = ch;
299 ch = stream->getChar();
300 } /* while */
302 stream->ungetChar();
303 ident[idx] = 0;
305 return ident; /* returns pointer to the token */
307 } /* linetoken */
309 /*************************** recognize *************************/
311 /* This function tries to match a string to a known list of
312 * valid AFM entries (check the keyStrings array above).
313 * "ident" contains everything from white space through the
314 * next space, tab, or ":" character.
316 * The algorithm is a standard Knuth binary search.
318 #if defined __clang__
319 #if __has_warning("-Wdeprecated-register")
320 #pragma GCC diagnostic push
321 #pragma GCC diagnostic ignored "-Wdeprecated-register"
322 #endif
323 #endif
324 #include "afm_hash.hpp"
325 #if defined __clang__
326 #if __has_warning("-Wdeprecated-register")
327 #pragma GCC diagnostic pop
328 #endif
329 #endif
331 static inline enum parseKey recognize( char* ident, int len)
333 const hash_entry* pEntry = AfmKeywordHash::in_word_set( ident, len );
334 return pEntry ? pEntry->eKey : NOPE;
336 } /* recognize */
338 /************************* parseGlobals *****************************/
340 /* This function is called by "parseFile". It will parse the AFM file
341 * up to the "StartCharMetrics" keyword, which essentially marks the
342 * end of the Global Font Information and the beginning of the character
343 * metrics information.
345 * If the caller of "parseFile" specified that it wanted the Global
346 * Font Information (as defined by the "AFM file Specification"
347 * document), then that information will be stored in the returned
348 * data structure.
350 * Any Global Font Information entries that are not found in a
351 * given file, will have the usual default initialization value
352 * for its type (i.e. entries of type int will be 0, etc).
354 * This function returns an error code specifying whether there was
355 * a premature EOF or a parsing error. This return value is used by
356 * parseFile to determine if there is more file to parse.
359 static int parseGlobals( FileInputStream* fp, GlobalFontInfo* gfi )
361 bool cont = true, save = (gfi != NULL);
362 int error = ok;
363 int direction = -1;
364 int tokenlen;
366 while (cont)
368 char *keyword = token(fp, tokenlen);
370 if (keyword == NULL)
371 /* Have reached an early and unexpected EOF. */
372 /* Set flag and stop parsing */
374 error = earlyEOF;
375 break; /* get out of loop */
377 if (!save)
378 /* get tokens until the end of the Global Font info section */
379 /* without saving any of the data */
380 switch (recognize(keyword, tokenlen))
382 case STARTCHARMETRICS:
383 cont = false;
384 break;
385 case ENDFONTMETRICS:
386 cont = false;
387 error = normalEOF;
388 break;
389 default:
390 break;
391 } /* switch */
392 else
393 /* otherwise parse entire global font info section, */
394 /* saving the data */
395 switch(recognize(keyword, tokenlen))
397 case STARTFONTMETRICS:
398 if ((keyword = token(fp,tokenlen)) != NULL)
399 gfi->afmVersion = strdup( keyword );
400 break;
401 case COMMENT:
402 linetoken(fp);
403 break;
404 case FONTNAME:
405 if ((keyword = token(fp,tokenlen)) != NULL)
406 gfi->fontName = strdup( keyword );
407 break;
408 case ENCODINGSCHEME:
409 if ((keyword = token(fp,tokenlen)) != NULL)
410 gfi->encodingScheme = strdup( keyword );
411 break;
412 case FULLNAME:
413 if ((keyword = linetoken(fp)) != NULL)
414 gfi->fullName = strdup( keyword );
415 break;
416 case FAMILYNAME:
417 if ((keyword = linetoken(fp)) != NULL)
418 gfi->familyName = strdup( keyword );
419 break;
420 case WEIGHT:
421 if ((keyword = token(fp,tokenlen)) != NULL)
422 gfi->weight = strdup( keyword );
423 break;
424 case ITALICANGLE:
425 if ((keyword = token(fp,tokenlen)) != NULL)
426 gfi->italicAngle = StringToDouble( keyword );
427 break;
428 case ISFIXEDPITCH:
429 if ((keyword = token(fp,tokenlen)) != NULL)
431 if (MATCH(keyword, False))
432 gfi->isFixedPitch = false;
433 else
434 gfi->isFixedPitch = true;
436 break;
437 case UNDERLINEPOSITION:
438 if ((keyword = token(fp,tokenlen)) != NULL)
439 gfi->underlinePosition = atoi(keyword);
440 break;
441 case UNDERLINETHICKNESS:
442 if ((keyword = token(fp,tokenlen)) != NULL)
443 gfi->underlineThickness = atoi(keyword);
444 break;
445 case VERSION:
446 if ((keyword = token(fp,tokenlen)) != NULL)
447 gfi->version = strdup( keyword );
448 break;
449 case NOTICE:
450 if ((keyword = linetoken(fp)) != NULL)
451 gfi->notice = strdup( keyword );
452 break;
453 case FONTBBOX:
454 if ((keyword = token(fp,tokenlen)) != NULL)
455 gfi->fontBBox.llx = atoi(keyword);
456 if ((keyword = token(fp,tokenlen)) != NULL)
457 gfi->fontBBox.lly = atoi(keyword);
458 if ((keyword = token(fp,tokenlen)) != NULL)
459 gfi->fontBBox.urx = atoi(keyword);
460 if ((keyword = token(fp,tokenlen)) != NULL)
461 gfi->fontBBox.ury = atoi(keyword);
462 break;
463 case CAPHEIGHT:
464 if ((keyword = token(fp,tokenlen)) != NULL)
465 gfi->capHeight = atoi(keyword);
466 break;
467 case XHEIGHT:
468 if ((keyword = token(fp,tokenlen)) != NULL)
469 gfi->xHeight = atoi(keyword);
470 break;
471 case DESCENT:
472 if ((keyword = token(fp,tokenlen)) != NULL)
473 gfi->descender = -atoi(keyword);
474 break;
475 case DESCENDER:
476 if ((keyword = token(fp,tokenlen)) != NULL)
477 gfi->descender = atoi(keyword);
478 break;
479 case ASCENT:
480 case ASCENDER:
481 if ((keyword = token(fp,tokenlen)) != NULL)
482 gfi->ascender = atoi(keyword);
483 break;
484 case STARTCHARMETRICS:
485 cont = false;
486 break;
487 case ENDFONTMETRICS:
488 cont = false;
489 error = normalEOF;
490 break;
491 case EM:
492 // skip one token
493 token(fp,tokenlen);
494 break;
495 case STARTDIRECTION:
496 if ((keyword = token(fp,tokenlen)) != NULL)
497 direction = atoi(keyword);
498 break; /* ignore this for now */
499 case ENDDIRECTION:
500 break; /* ignore this for now */
501 case MAPPINGSCHEME:
502 token(fp,tokenlen);
503 break; /* ignore this for now */
504 case CHARACTERS:
505 token(fp,tokenlen);
506 break; /* ignore this for now */
507 case ISBASEFONT:
508 token(fp,tokenlen);
509 break; /* ignore this for now */
510 case CHARACTERSET:
511 token(fp,tokenlen); //ignore
512 break;
513 case STDHW:
514 token(fp,tokenlen); //ignore
515 break;
516 case STDVW:
517 token(fp,tokenlen); //ignore
518 break;
519 case CHARWIDTH:
520 if ((keyword = token(fp,tokenlen)) != NULL)
522 if (direction == 0)
523 gfi->charwidth = atoi(keyword);
525 token(fp,tokenlen);
526 /* ignore y-width for now */
527 break;
528 case METRICSSETS:
529 token(fp,tokenlen); /*eat token*/
530 break; /* ignore this for now */
531 case NOPE:
532 default:
533 error = parseError;
534 break;
535 } /* switch */
536 } /* while */
538 return error;
540 } /* parseGlobals */
542 /************************* parseCharWidths **************************/
544 /* This function is called by "parseFile". It will parse the AFM file
545 * up to the "EndCharMetrics" keyword. It will save the character
546 * width info (as opposed to all of the character metric information)
547 * if requested by the caller of parseFile. Otherwise, it will just
548 * parse through the section without saving any information.
550 * If data is to be saved, parseCharWidths is passed in a pointer
551 * to an array of widths that has already been initialized by the
552 * standard value for unmapped character codes. This function parses
553 * the Character Metrics section only storing the width information
554 * for the encoded characters into the array using the character code
555 * as the index into that array.
557 * This function returns an error code specifying whether there was
558 * a premature EOF or a parsing error. This return value is used by
559 * parseFile to determine if there is more file to parse.
562 static int parseCharWidths( FileInputStream* fp, int* cwi)
564 bool cont = true, save = (cwi != NULL);
565 int pos = 0, error = ok, tokenlen;
567 while (cont)
569 char *keyword = token(fp,tokenlen);
570 /* Have reached an early and unexpected EOF. */
571 /* Set flag and stop parsing */
572 if (keyword == NULL)
574 error = earlyEOF;
575 break; /* get out of loop */
577 if (!save)
578 /* get tokens until the end of the Char Metrics section without */
579 /* saving any of the data*/
580 switch (recognize(keyword,tokenlen))
582 case ENDCHARMETRICS:
583 cont = false;
584 break;
585 case ENDFONTMETRICS:
586 cont = false;
587 error = normalEOF;
588 break;
589 default:
590 break;
591 } /* switch */
592 else
593 /* otherwise parse entire char metrics section, saving */
594 /* only the char x-width info */
595 switch(recognize(keyword,tokenlen))
597 case COMMENT:
598 linetoken(fp); /*eat token*/
599 break;
600 case CODE:
601 if ((keyword = token(fp,tokenlen)) != NULL)
602 pos = atoi(keyword);
603 break;
604 case XYWIDTH:
605 /* PROBLEM: Should be no Y-WIDTH when doing "quick & dirty" */
606 token(fp,tokenlen); token(fp,tokenlen); /* eat values */
607 error = parseError;
608 break;
609 case CODEHEX:
610 if ((keyword = token(fp,tokenlen)) != NULL)
611 sscanf(keyword, "<%x>", &pos);
612 break;
613 case X0WIDTH:
614 (void) token(fp,tokenlen);
615 break;
616 case XWIDTH:
617 if ((keyword = token(fp,tokenlen)) != NULL)
618 if (pos >= 0) /* ignore unmapped chars */
619 cwi[pos] = atoi(keyword);
620 break;
621 case ENDCHARMETRICS:
622 cont = false;
623 break;
624 case ENDFONTMETRICS:
625 cont = false;
626 error = normalEOF;
627 break;
628 case CHARNAME: /* eat values (so doesn't cause parseError) */
629 token(fp,tokenlen);
630 break;
631 case CHARBBOX:
632 token(fp,tokenlen); token(fp,tokenlen);
633 token(fp,tokenlen); token(fp,tokenlen);
634 break;
635 case LIGATURE:
636 token(fp,tokenlen); token(fp,tokenlen);
637 break;
638 case VVECTOR:
639 token(fp,tokenlen); /*eat token*/
640 token(fp,tokenlen); /*eat token*/
641 break;
642 case NOPE:
643 default:
644 error = parseError;
645 break;
646 } /* switch */
647 } /* while */
649 return error;
651 } /* parseCharWidths */
654 * number of char metrics is almost always inaccurate, so be gentle and try to
655 * adapt our internal storage by adjusting the allocated list
658 static int
659 reallocFontMetrics( void **pp_fontmetrics, int *p_oldcount, int n_newcount, unsigned int n_size )
661 char *p_tmpmetrics = NULL;
663 if ((pp_fontmetrics == NULL) || (*pp_fontmetrics == NULL))
664 return storageProblem;
666 if (*p_oldcount == n_newcount)
667 return ok;
669 p_tmpmetrics = static_cast<char*>(realloc(*pp_fontmetrics, n_newcount * n_size));
670 if (p_tmpmetrics == NULL)
671 return storageProblem;
673 if ( n_newcount > *p_oldcount )
675 char *p_inimetrics = p_tmpmetrics + n_size * *p_oldcount;
676 int n_inimetrics = n_size * (n_newcount - *p_oldcount);
677 memset( p_inimetrics, 0, n_inimetrics );
680 *pp_fontmetrics = p_tmpmetrics;
681 *p_oldcount = n_newcount;
683 return ok;
686 static unsigned int
687 enlargeCount( unsigned int n_oldcount )
689 unsigned int n_newcount = n_oldcount + n_oldcount / 5;
690 if (n_oldcount == n_newcount )
691 n_newcount = n_oldcount + 5;
693 return n_newcount;
696 /************************* parseCharMetrics ************************/
698 /* This function is called by parseFile if the caller of parseFile
699 * requested that all character metric information be saved
700 * (as opposed to only the character width information).
702 * parseCharMetrics is passed in a pointer to an array of records
703 * to hold information on a per character basis. This function
704 * parses the Character Metrics section storing all character
705 * metric information for the ALL characters (mapped and unmapped)
706 * into the array.
708 * This function returns an error code specifying whether there was
709 * a premature EOF or a parsing error. This return value is used by
710 * parseFile to determine if there is more file to parse.
713 static int parseCharMetrics( FileInputStream* fp, FontInfo* fi)
715 bool cont = true, firstTime = true;
716 int error = ok, count = 0, tokenlen;
717 CharMetricInfo *temp = fi->cmi;
719 while (cont)
721 char *keyword = token(fp,tokenlen);
722 if (keyword == NULL)
724 error = earlyEOF;
725 break; /* get out of loop */
727 switch(recognize(keyword,tokenlen))
729 case COMMENT:
730 linetoken(fp); /*eat token*/
731 break;
732 case CODE:
733 if (!(count < fi->numOfChars))
735 reallocFontMetrics( reinterpret_cast<void**>(&fi->cmi),
736 &(fi->numOfChars), enlargeCount(fi->numOfChars),
737 sizeof(CharMetricInfo) );
738 temp = &(fi->cmi[ count - 1 ]);
740 if (count < fi->numOfChars)
742 if (firstTime) firstTime = false;
743 else temp++;
744 if ((keyword = token(fp,tokenlen)) != NULL)
745 temp->code = atoi(keyword);
746 if (fi->gfi && fi->gfi->charwidth)
747 temp->wx = fi->gfi->charwidth;
748 count++;
750 else
752 error = parseError;
753 cont = false;
755 break;
756 case CODEHEX:
757 if (!(count < fi->numOfChars ))
759 reallocFontMetrics( reinterpret_cast<void**>(&fi->cmi),
760 &(fi->numOfChars), enlargeCount(fi->numOfChars),
761 sizeof(CharMetricInfo) );
762 temp = &(fi->cmi[ count - 1 ]);
764 if (count < fi->numOfChars) {
765 if (firstTime)
766 firstTime = false;
767 else
768 temp++;
769 if ((keyword = token(fp,tokenlen)) != NULL)
770 sscanf(keyword,"<%x>", &temp->code);
771 if (fi->gfi && fi->gfi->charwidth)
772 temp->wx = fi->gfi->charwidth;
773 count++;
775 else {
776 error = parseError;
777 cont = false;
779 break;
780 case XYWIDTH:
781 if ((keyword = token(fp,tokenlen)) != NULL)
782 temp->wx = atoi(keyword);
783 if ((keyword = token(fp,tokenlen)) != NULL)
784 temp->wy = atoi(keyword);
785 break;
786 case X0WIDTH:
787 if ((keyword = token(fp,tokenlen)) != NULL)
788 temp->wx = atoi(keyword);
789 break;
790 case XWIDTH:
791 if ((keyword = token(fp,tokenlen)) != NULL)
792 temp->wx = atoi(keyword);
793 break;
794 case CHARNAME:
795 if ((keyword = token(fp,tokenlen)) != NULL)
796 temp->name = strdup(keyword);
797 break;
798 case CHARBBOX:
799 if ((keyword = token(fp,tokenlen)) != NULL)
800 temp->charBBox.llx = atoi(keyword);
801 if ((keyword = token(fp,tokenlen)) != NULL)
802 temp->charBBox.lly = atoi(keyword);
803 if ((keyword = token(fp,tokenlen)) != NULL)
804 temp->charBBox.urx = atoi(keyword);
805 if ((keyword = token(fp,tokenlen)) != NULL)
806 temp->charBBox.ury = atoi(keyword);
807 break;
808 case LIGATURE: {
809 Ligature **tail = &(temp->ligs);
810 Ligature *node = *tail;
812 if (*tail != NULL)
814 while (node->next != NULL)
815 node = node->next;
816 tail = &(node->next);
819 *tail = static_cast<Ligature *>(calloc(1, sizeof(Ligature)));
820 if ((keyword = token(fp,tokenlen)) != NULL)
821 (*tail)->succ = strdup(keyword);
822 if ((keyword = token(fp,tokenlen)) != NULL)
823 (*tail)->lig = strdup(keyword);
824 break; }
825 case ENDCHARMETRICS:
826 cont = false;
827 break;
828 case ENDFONTMETRICS:
829 cont = false;
830 error = normalEOF;
831 break;
832 case VVECTOR:
833 token(fp,tokenlen); /*eat token*/
834 token(fp,tokenlen); /*eat token*/
835 break;
836 case NOPE:
837 default:
838 error = parseError;
839 break;
840 } /* switch */
841 } /* while */
843 if ((error == ok) && (count != fi->numOfChars))
844 error = reallocFontMetrics( reinterpret_cast<void**>(&fi->cmi), &(fi->numOfChars),
845 count, sizeof(CharMetricInfo) );
847 if ((error == ok) && (count != fi->numOfChars))
848 error = parseError;
850 return error;
852 } /* parseCharMetrics */
854 /************************* parseTrackKernData ***********************/
856 /* This function is called by "parseFile". It will parse the AFM file
857 * up to the "EndTrackKern" or "EndKernData" keywords. It will save the
858 * track kerning data if requested by the caller of parseFile.
860 * parseTrackKernData is passed in a pointer to the FontInfo record.
861 * If data is to be saved, the FontInfo record will already contain
862 * a valid pointer to storage for the track kerning data.
864 * This function returns an error code specifying whether there was
865 * a premature EOF or a parsing error. This return value is used by
866 * parseFile to determine if there is more file to parse.
869 static int parseTrackKernData( FileInputStream* fp, FontInfo* fi)
871 bool cont = true, save = (fi->tkd != NULL);
872 int pos = 0, error = ok, tcount = 0, tokenlen;
874 while (cont)
876 char *keyword = token(fp,tokenlen);
878 if (keyword == NULL)
880 error = earlyEOF;
881 break; /* get out of loop */
883 if (!save)
884 /* get tokens until the end of the Track Kerning Data */
885 /* section without saving any of the data */
886 switch(recognize(keyword,tokenlen))
888 case ENDTRACKKERN:
889 case ENDKERNDATA:
890 cont = false;
891 break;
892 case ENDFONTMETRICS:
893 cont = false;
894 error = normalEOF;
895 break;
896 default:
897 break;
898 } /* switch */
899 else
900 /* otherwise parse entire Track Kerning Data section, */
901 /* saving the data */
902 switch(recognize(keyword,tokenlen))
904 case COMMENT:
905 linetoken(fp); /*eat token*/
906 break;
907 case TRACKKERN:
908 if (!(tcount < fi->numOfTracks))
910 reallocFontMetrics( reinterpret_cast<void**>(&fi->tkd), &(fi->numOfTracks),
911 enlargeCount(fi->numOfTracks), sizeof(TrackKernData) );
914 if (tcount < fi->numOfTracks)
916 if ((keyword = token(fp,tokenlen)) != NULL)
917 fi->tkd[pos].degree = atoi(keyword);
918 if ((keyword = token(fp,tokenlen)) != NULL)
919 fi->tkd[pos].minPtSize = StringToDouble(keyword);
920 if ((keyword = token(fp,tokenlen)) != NULL)
921 fi->tkd[pos].minKernAmt = StringToDouble(keyword);
922 if ((keyword = token(fp,tokenlen)) != NULL)
923 fi->tkd[pos].maxPtSize = StringToDouble(keyword);
924 if ((keyword = token(fp,tokenlen)) != NULL)
925 fi->tkd[pos++].maxKernAmt = StringToDouble(keyword);
926 tcount++;
928 else
930 error = parseError;
931 cont = false;
933 break;
934 case ENDTRACKKERN:
935 case ENDKERNDATA:
936 cont = false;
937 break;
938 case ENDFONTMETRICS:
939 cont = false;
940 error = normalEOF;
941 break;
942 case NOPE:
943 default:
944 error = parseError;
945 break;
946 } /* switch */
947 } /* while */
949 if (error == ok && tcount != fi->numOfTracks)
950 error = reallocFontMetrics( reinterpret_cast<void**>(&fi->tkd), &(fi->numOfTracks),
951 tcount, sizeof(TrackKernData) );
953 if (error == ok && tcount != fi->numOfTracks)
954 error = parseError;
956 return error;
958 } /* parseTrackKernData */
960 /************************* parsePairKernData ************************/
962 /* This function is called by "parseFile". It will parse the AFM file
963 * up to the "EndKernPairs" or "EndKernData" keywords. It will save
964 * the pair kerning data if requested by the caller of parseFile.
966 * parsePairKernData is passed in a pointer to the FontInfo record.
967 * If data is to be saved, the FontInfo record will already contain
968 * a valid pointer to storage for the pair kerning data.
970 * This function returns an error code specifying whether there was
971 * a premature EOF or a parsing error. This return value is used by
972 * parseFile to determine if there is more file to parse.
975 static int parsePairKernData( FileInputStream* fp, FontInfo* fi)
977 bool cont = true, save = (fi->pkd != NULL);
978 int pos = 0, error = ok, pcount = 0, tokenlen;
980 while (cont)
982 char *keyword = token(fp,tokenlen);
984 if (keyword == NULL)
986 error = earlyEOF;
987 break; /* get out of loop */
989 if (!save)
990 /* get tokens until the end of the Pair Kerning Data */
991 /* section without saving any of the data */
992 switch(recognize(keyword,tokenlen))
994 case ENDKERNPAIRS:
995 case ENDKERNDATA:
996 cont = false;
997 break;
998 case ENDFONTMETRICS:
999 cont = false;
1000 error = normalEOF;
1001 break;
1002 default:
1003 break;
1004 } /* switch */
1005 else
1006 /* otherwise parse entire Pair Kerning Data section, */
1007 /* saving the data */
1008 switch(recognize(keyword,tokenlen))
1010 case COMMENT:
1011 linetoken(fp); /*eat token*/
1012 break;
1013 case KERNPAIR:
1014 if (!(pcount < fi->numOfPairs))
1016 reallocFontMetrics( reinterpret_cast<void**>(&fi->pkd), &(fi->numOfPairs),
1017 enlargeCount(fi->numOfPairs), sizeof(PairKernData) );
1019 if (pcount < fi->numOfPairs)
1021 if ((keyword = token(fp,tokenlen)) != NULL)
1022 fi->pkd[pos].name1 = strdup( keyword );
1023 if ((keyword = token(fp,tokenlen)) != NULL)
1024 fi->pkd[pos].name2 = strdup( keyword );
1025 if ((keyword = token(fp,tokenlen)) != NULL)
1026 fi->pkd[pos].xamt = atoi(keyword);
1027 if ((keyword = token(fp,tokenlen)) != NULL)
1028 fi->pkd[pos++].yamt = atoi(keyword);
1029 pcount++;
1031 else
1033 error = parseError;
1034 cont = false;
1036 break;
1037 case KERNPAIRXAMT:
1038 if (!(pcount < fi->numOfPairs))
1040 reallocFontMetrics( reinterpret_cast<void**>(&fi->pkd), &(fi->numOfPairs),
1041 enlargeCount(fi->numOfPairs), sizeof(PairKernData) );
1043 if (pcount < fi->numOfPairs)
1045 if ((keyword = token(fp,tokenlen)) != NULL)
1046 fi->pkd[pos].name1 = strdup( keyword );
1047 if ((keyword = token(fp,tokenlen)) != NULL)
1048 fi->pkd[pos].name2 = strdup( keyword );
1049 if ((keyword = token(fp,tokenlen)) != NULL)
1050 fi->pkd[pos++].xamt = atoi(keyword);
1051 pcount++;
1053 else
1055 error = parseError;
1056 cont = false;
1058 break;
1059 case ENDKERNPAIRS:
1060 case ENDKERNDATA:
1061 cont = false;
1062 break;
1063 case ENDFONTMETRICS:
1064 cont = false;
1065 error = normalEOF;
1066 break;
1067 case NOPE:
1068 default:
1069 error = parseError;
1070 break;
1071 } /* switch */
1072 } /* while */
1074 if ((error == ok) && (pcount != fi->numOfPairs))
1075 error = reallocFontMetrics( reinterpret_cast<void**>(&fi->pkd), &(fi->numOfPairs),
1076 pcount, sizeof(PairKernData) );
1078 if (error == ok && pcount != fi->numOfPairs)
1079 error = parseError;
1081 return error;
1083 } /* parsePairKernData */
1085 /************************* parseCompCharData **************************/
1087 /* This function is called by "parseFile". It will parse the AFM file
1088 * up to the "EndComposites" keyword. It will save the composite
1089 * character data if requested by the caller of parseFile.
1091 * parseCompCharData is passed in a pointer to the FontInfo record, and
1092 * a boolean representing if the data should be saved.
1094 * This function will create the appropriate amount of storage for
1095 * the composite character data and store a pointer to the storage
1096 * in the FontInfo record.
1098 * This function returns an error code specifying whether there was
1099 * a premature EOF or a parsing error. This return value is used by
1100 * parseFile to determine if there is more file to parse.
1103 static int parseCompCharData( FileInputStream* fp, FontInfo* fi)
1105 bool cont = true, firstTime = true, save = (fi->ccd != NULL);
1106 int pos = 0, j = 0, error = ok, ccount = 0, pcount = 0, tokenlen;
1108 while (cont)
1110 char *keyword = token(fp,tokenlen);
1111 if (keyword == NULL)
1112 /* Have reached an early and unexpected EOF. */
1113 /* Set flag and stop parsing */
1115 error = earlyEOF;
1116 break; /* get out of loop */
1118 if (ccount > fi->numOfComps)
1120 reallocFontMetrics( reinterpret_cast<void**>(&fi->ccd), &(fi->numOfComps),
1121 enlargeCount(fi->numOfComps), sizeof(CompCharData) );
1123 if (ccount > fi->numOfComps)
1125 error = parseError;
1126 break; /* get out of loop */
1128 if (!save)
1129 /* get tokens until the end of the Composite Character info */
1130 /* section without saving any of the data */
1131 switch(recognize(keyword,tokenlen))
1133 case ENDCOMPOSITES:
1134 cont = false;
1135 break;
1136 case ENDFONTMETRICS:
1137 cont = false;
1138 error = normalEOF;
1139 break;
1140 case COMMENT:
1141 case COMPCHAR:
1142 linetoken(fp);
1143 break;
1144 default:
1145 break;
1146 } /* switch */
1147 else
1148 /* otherwise parse entire Composite Character info section, */
1149 /* saving the data */
1150 switch(recognize(keyword,tokenlen))
1152 case COMMENT:
1153 linetoken(fp); /*eat token*/
1154 break;
1155 case COMPCHAR:
1156 if (!(ccount < fi->numOfComps))
1158 reallocFontMetrics( reinterpret_cast<void**>(&fi->ccd), &(fi->numOfComps),
1159 enlargeCount(fi->numOfComps), sizeof(CompCharData) );
1161 if (ccount < fi->numOfComps)
1163 keyword = token(fp,tokenlen);
1164 if (pcount != fi->ccd[pos].numOfPieces)
1165 error = parseError;
1166 pcount = 0;
1167 if (firstTime) firstTime = false;
1168 else pos++;
1169 fi->ccd[pos].ccName = strdup( keyword );
1170 if ((keyword = token(fp,tokenlen)) != NULL)
1171 fi->ccd[pos].numOfPieces = atoi(keyword);
1172 fi->ccd[pos].pieces = static_cast<Pcc *>(
1173 calloc(fi->ccd[pos].numOfPieces, sizeof(Pcc)));
1174 j = 0;
1175 ccount++;
1177 else
1179 error = parseError;
1180 cont = false;
1182 break;
1183 case COMPCHARPIECE:
1184 if (pcount < fi->ccd[pos].numOfPieces)
1186 if ((keyword = token(fp,tokenlen)) != NULL)
1187 fi->ccd[pos].pieces[j].pccName = strdup( keyword );
1188 if ((keyword = token(fp,tokenlen)) != NULL)
1189 fi->ccd[pos].pieces[j].deltax = atoi(keyword);
1190 if ((keyword = token(fp,tokenlen)) != NULL)
1191 fi->ccd[pos].pieces[j++].deltay = atoi(keyword);
1192 pcount++;
1194 else
1195 error = parseError;
1196 break;
1197 case ENDCOMPOSITES:
1198 cont = false;
1199 break;
1200 case ENDFONTMETRICS:
1201 cont = false;
1202 error = normalEOF;
1203 break;
1204 case NOPE:
1205 default:
1206 error = parseError;
1207 break;
1208 } /* switch */
1209 } /* while */
1211 if (error == ok && ccount != fi->numOfComps)
1212 reallocFontMetrics( reinterpret_cast<void**>(&fi->ccd), &(fi->numOfComps),
1213 ccount, sizeof(CompCharData) );
1215 if (error == ok && ccount != fi->numOfComps)
1216 error = parseError;
1218 return error;
1220 } /* parseCompCharData */
1222 /*************************** 'PUBLIC' FUNCTION ********************/
1224 /*************************** parseFile *****************************/
1226 /* parseFile is the only 'public' procedure available. It is called
1227 * from an application wishing to get information from an AFM file.
1228 * The caller of this function is responsible for locating and opening
1229 * an AFM file and handling all errors associated with that task.
1231 * parseFile expects 3 parameters: a filename pointer, a pointer
1232 * to a (FontInfo *) variable (for which storage will be allocated and
1233 * the data requested filled in), and a mask specifying which
1234 * data from the AFM file should be saved in the FontInfo structure.
1236 * The file will be parsed and the requested data will be stored in
1237 * a record of type FontInfo (refer to ParseAFM.h).
1239 * parseFile returns an error code as defined in parseAFM.h.
1241 * The position of the read/write pointer associated with the file
1242 * pointer upon return of this function is undefined.
1245 int parseFile( const char* pFilename, FontInfo** fi, FLAGS flags)
1247 FileInputStream aFile( pFilename );
1249 int code = ok; /* return code from each of the parsing routines */
1250 int error = ok; /* used as the return code from this function */
1251 int tokenlen;
1253 char *keyword; /* used to store a token */
1255 (*fi) = static_cast<FontInfo *>(calloc(1, sizeof(FontInfo)));
1256 if ((*fi) == NULL) { error = storageProblem; return error; }
1258 if (flags & P_G)
1260 (*fi)->gfi = static_cast<GlobalFontInfo *>(calloc(1, sizeof(GlobalFontInfo)));
1261 if ((*fi)->gfi == NULL) { error = storageProblem; return error; }
1264 /* The AFM file begins with Global Font Information. This section */
1265 /* will be parsed whether or not information should be saved. */
1266 code = parseGlobals(&aFile, (*fi)->gfi);
1268 if (code < 0) error = code;
1270 /* The Global Font Information is followed by the Character Metrics */
1271 /* section. Which procedure is used to parse this section depends on */
1272 /* how much information should be saved. If all of the metrics info */
1273 /* is wanted, parseCharMetrics is called. If only the character widths */
1274 /* is wanted, parseCharWidths is called. parseCharWidths will also */
1275 /* be called in the case that no character data is to be saved, just */
1276 /* to parse through the section. */
1278 if ((code != normalEOF) && (code != earlyEOF))
1280 if ((keyword = token(&aFile,tokenlen)) != NULL)
1281 (*fi)->numOfChars = atoi(keyword);
1282 if (flags & (P_M ^ P_W))
1284 (*fi)->cmi = static_cast<CharMetricInfo *>(
1285 calloc((*fi)->numOfChars, sizeof(CharMetricInfo)));
1286 if ((*fi)->cmi == NULL) { error = storageProblem; return error; }
1287 code = parseCharMetrics(&aFile, *fi);
1289 else
1291 if (flags & P_W)
1293 (*fi)->cwi = static_cast<int *>(calloc(256, sizeof(int)));
1294 if ((*fi)->cwi == NULL)
1296 error = storageProblem;
1297 return error;
1300 /* parse section regardless */
1301 code = parseCharWidths(&aFile, (*fi)->cwi);
1302 } /* else */
1303 } /* if */
1305 if ((error != earlyEOF) && (code < 0)) error = code;
1307 /* The remaining sections of the AFM are optional. This code will */
1308 /* look at the next keyword in the file to determine what section */
1309 /* is next, and then allocate the appropriate amount of storage */
1310 /* for the data (if the data is to be saved) and call the */
1311 /* appropriate parsing routine to parse the section. */
1313 while ((code != normalEOF) && (code != earlyEOF))
1315 keyword = token(&aFile,tokenlen);
1316 if (keyword == NULL)
1317 /* Have reached an early and unexpected EOF. */
1318 /* Set flag and stop parsing */
1320 code = earlyEOF;
1321 break; /* get out of loop */
1323 switch(recognize(keyword,tokenlen))
1325 case STARTKERNDATA:
1326 break;
1327 case ENDKERNDATA:
1328 break;
1329 case STARTTRACKKERN:
1330 keyword = token(&aFile,tokenlen);
1331 if ((flags & P_T) && keyword)
1333 (*fi)->numOfTracks = atoi(keyword);
1334 (*fi)->tkd = static_cast<TrackKernData *>(
1335 calloc((*fi)->numOfTracks, sizeof(TrackKernData)));
1336 if ((*fi)->tkd == NULL)
1338 error = storageProblem;
1339 return error;
1341 } /* if */
1342 code = parseTrackKernData(&aFile, *fi);
1343 break;
1344 case STARTKERNPAIRS:
1345 keyword = token(&aFile,tokenlen);
1346 if ((flags & P_P) && keyword)
1348 (*fi)->numOfPairs = atoi(keyword);
1349 (*fi)->pkd = static_cast<PairKernData *>(
1350 calloc((*fi)->numOfPairs, sizeof(PairKernData)));
1351 if ((*fi)->pkd == NULL)
1353 error = storageProblem;
1354 return error;
1356 } /* if */
1357 code = parsePairKernData(&aFile, *fi);
1358 break;
1359 case STARTCOMPOSITES:
1360 keyword = token(&aFile,tokenlen);
1361 if ((flags & P_C) && keyword)
1363 (*fi)->numOfComps = atoi(keyword);
1364 (*fi)->ccd = static_cast<CompCharData *>(
1365 calloc((*fi)->numOfComps, sizeof(CompCharData)));
1366 if ((*fi)->ccd == NULL)
1368 error = storageProblem;
1369 return error;
1371 } /* if */
1372 code = parseCompCharData(&aFile, *fi);
1373 break;
1374 case ENDFONTMETRICS:
1375 code = normalEOF;
1376 break;
1377 case COMMENT:
1378 linetoken(&aFile);
1379 break;
1380 case NOPE:
1381 default:
1382 code = parseError;
1383 break;
1384 } /* switch */
1386 if ((error != earlyEOF) && (code < 0)) error = code;
1388 } /* while */
1390 if ((error != earlyEOF) && (code < 0)) error = code;
1392 return error;
1394 } /* parseFile */
1396 void
1397 freeFontInfo (FontInfo *fi)
1399 int i;
1401 if (fi->gfi)
1403 free (fi->gfi->afmVersion);
1404 free (fi->gfi->fontName);
1405 free (fi->gfi->fullName);
1406 free (fi->gfi->familyName);
1407 free (fi->gfi->weight);
1408 free (fi->gfi->version);
1409 free (fi->gfi->notice);
1410 free (fi->gfi->encodingScheme);
1411 free (fi->gfi);
1414 free (fi->cwi);
1416 if (fi->cmi)
1418 for (i = 0; i < fi->numOfChars; i++)
1420 Ligature *ligs;
1421 free (fi->cmi[i].name);
1422 ligs = fi->cmi[i].ligs;
1423 while (ligs)
1425 Ligature *tmp;
1426 tmp = ligs;
1427 ligs = ligs->next;
1428 free (tmp->succ);
1429 free (tmp->lig);
1430 free (tmp);
1433 free (fi->cmi);
1436 free (fi->tkd);
1438 if (fi->pkd)
1440 for ( i = 0; i < fi->numOfPairs; i++)
1442 free (fi->pkd[i].name1);
1443 free (fi->pkd[i].name2);
1445 free (fi->pkd);
1448 if (fi->ccd)
1450 for (i = 0; i < fi->numOfComps; i++)
1452 free (fi->ccd[i].ccName);
1453 int j;
1454 for (j = 0; j < fi->ccd[i].numOfPieces; j++)
1455 free (fi->ccd[i].pieces[j].pccName);
1457 free (fi->ccd[i].pieces);
1459 free (fi->ccd);
1462 free (fi);
1465 } // namspace
1467 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */