1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: AFMParser.java 1761019 2016-09-16 10:43:45Z ssteiner $ */
19 
20 package org.apache.fop.fonts.type1;
21 
22 import java.awt.Rectangle;
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.Reader;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Stack;
32 
33 import org.apache.commons.io.IOUtils;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 
37 import org.apache.fop.fonts.NamedCharacter;
38 
39 /**
40  * Parses the contents of a Type 1 AFM font metrics file into an object structure ({@link AFMFile}).
41  */
42 public class AFMParser {
43 
44     private static Log log = LogFactory.getLog(AFMParser.class);
45 
46     private static final String START_FONT_METRICS = "StartFontMetrics";
47     //private static final String END_FONT_METRICS = "EndFontMetrics";
48     private static final String FONT_NAME = "FontName";
49     private static final String FULL_NAME = "FullName";
50     private static final String FAMILY_NAME = "FamilyName";
51     private static final String WEIGHT = "Weight";
52     private static final String FONT_BBOX = "FontBBox";
53     private static final String ENCODING_SCHEME = "EncodingScheme";
54     private static final String CHARACTER_SET = "CharacterSet";
55     private static final String IS_BASE_FONT = "IsBaseFont";
56     private static final String IS_CID_FONT = "IsCIDFont";
57     private static final String CAP_HEIGHT = "CapHeight";
58     private static final String X_HEIGHT = "XHeight";
59     private static final String ASCENDER = "Ascender";
60     private static final String DESCENDER = "Descender";
61     private static final String STDHW = "StdHW";
62     private static final String STDVW = "StdVW";
63     private static final String UNDERLINE_POSITION = "UnderlinePosition";
64     private static final String UNDERLINE_THICKNESS = "UnderlineThickness";
65     private static final String ITALIC_ANGLE = "ItalicAngle";
66     private static final String IS_FIXED_PITCH = "IsFixedPitch";
67     private static final String START_DIRECTION = "StartDirection";
68     private static final String END_DIRECTION = "EndDirection";
69     private static final String START_CHAR_METRICS = "StartCharMetrics";
70     private static final String END_CHAR_METRICS = "EndCharMetrics";
71     private static final String C = "C";
72     private static final String CH = "CH";
73     private static final String WX = "WX";
74     private static final String W0X = "W0X";
75     private static final String W1X = "W1X";
76     private static final String WY = "WY";
77     private static final String W0Y = "W0Y";
78     private static final String W1Y = "W1Y";
79     private static final String W = "W";
80     private static final String W0 = "W0";
81     private static final String W1 = "W1";
82     private static final String N = "N";
83     private static final String B = "B";
84     private static final String START_TRACK_KERN = "StartTrackKern";
85     private static final String END_TRACK_KERN = "EndTrackKern";
86     //private static final String START_KERN_PAIRS = "StartKernPairs";
87     //private static final String START_KERN_PAIRS0 = "StartKernPairs0";
88     private static final String START_KERN_PAIRS1 = "StartKernPairs1";
89     //private static final String END_KERN_PAIRS = "EndKernPairs";
90     private static final String START_COMPOSITES = "StartComposites";
91     private static final String START_COMP_FONT_METRICS = "StartCompFontMetrics";
92 
93     private static final String KP = "KP";
94     private static final String KPH = "KPH";
95     private static final String KPX = "KPX";
96     private static final String KPY = "KPY";
97 
98     private static final int PARSE_NORMAL = 0;
99     private static final int PARSE_CHAR_METRICS = 1;
100 
101     private static final Map<String, ValueHandler> VALUE_PARSERS;
102     private static final Map<String, Integer> PARSE_MODE_CHANGES;
103 
104     static {
105         VALUE_PARSERS = new HashMap<String, ValueHandler>();
VALUE_PARSERS.put(START_FONT_METRICS, new StartFontMetrics())106         VALUE_PARSERS.put(START_FONT_METRICS, new StartFontMetrics());
VALUE_PARSERS.put(FONT_NAME, new StringSetter(FONT_NAME))107         VALUE_PARSERS.put(FONT_NAME, new StringSetter(FONT_NAME));
VALUE_PARSERS.put(FULL_NAME, new StringSetter(FULL_NAME))108         VALUE_PARSERS.put(FULL_NAME, new StringSetter(FULL_NAME));
VALUE_PARSERS.put(FAMILY_NAME, new StringSetter(FAMILY_NAME))109         VALUE_PARSERS.put(FAMILY_NAME, new StringSetter(FAMILY_NAME));
VALUE_PARSERS.put(WEIGHT, new StringSetter(WEIGHT))110         VALUE_PARSERS.put(WEIGHT, new StringSetter(WEIGHT));
VALUE_PARSERS.put(ENCODING_SCHEME, new StringSetter(ENCODING_SCHEME))111         VALUE_PARSERS.put(ENCODING_SCHEME, new StringSetter(ENCODING_SCHEME));
VALUE_PARSERS.put(FONT_BBOX, new FontBBox())112         VALUE_PARSERS.put(FONT_BBOX, new FontBBox());
VALUE_PARSERS.put(CHARACTER_SET, new StringSetter(CHARACTER_SET))113         VALUE_PARSERS.put(CHARACTER_SET, new StringSetter(CHARACTER_SET));
VALUE_PARSERS.put(IS_BASE_FONT, new IsBaseFont())114         VALUE_PARSERS.put(IS_BASE_FONT, new IsBaseFont());
VALUE_PARSERS.put(IS_CID_FONT, new IsCIDFont())115         VALUE_PARSERS.put(IS_CID_FONT, new IsCIDFont());
VALUE_PARSERS.put(CAP_HEIGHT, new NumberSetter(CAP_HEIGHT))116         VALUE_PARSERS.put(CAP_HEIGHT, new NumberSetter(CAP_HEIGHT));
VALUE_PARSERS.put(X_HEIGHT, new NumberSetter(X_HEIGHT))117         VALUE_PARSERS.put(X_HEIGHT, new NumberSetter(X_HEIGHT));
VALUE_PARSERS.put(ASCENDER, new NumberSetter(ASCENDER))118         VALUE_PARSERS.put(ASCENDER, new NumberSetter(ASCENDER));
VALUE_PARSERS.put(DESCENDER, new NumberSetter(DESCENDER))119         VALUE_PARSERS.put(DESCENDER, new NumberSetter(DESCENDER));
VALUE_PARSERS.put(STDHW, new NumberSetter(STDHW))120         VALUE_PARSERS.put(STDHW, new NumberSetter(STDHW));
VALUE_PARSERS.put(STDVW, new NumberSetter(STDVW))121         VALUE_PARSERS.put(STDVW, new NumberSetter(STDVW));
VALUE_PARSERS.put(START_DIRECTION, new StartDirection())122         VALUE_PARSERS.put(START_DIRECTION, new StartDirection());
VALUE_PARSERS.put(END_DIRECTION, new EndDirection())123         VALUE_PARSERS.put(END_DIRECTION, new EndDirection());
VALUE_PARSERS.put(UNDERLINE_POSITION, new WritingDirNumberSetter(UNDERLINE_POSITION))124         VALUE_PARSERS.put(UNDERLINE_POSITION, new WritingDirNumberSetter(UNDERLINE_POSITION));
VALUE_PARSERS.put(UNDERLINE_THICKNESS, new WritingDirNumberSetter(UNDERLINE_THICKNESS))125         VALUE_PARSERS.put(UNDERLINE_THICKNESS, new WritingDirNumberSetter(UNDERLINE_THICKNESS));
VALUE_PARSERS.put(ITALIC_ANGLE, new WritingDirDoubleSetter(ITALIC_ANGLE))126         VALUE_PARSERS.put(ITALIC_ANGLE, new WritingDirDoubleSetter(ITALIC_ANGLE));
VALUE_PARSERS.put(IS_FIXED_PITCH, new WritingDirBooleanSetter(IS_FIXED_PITCH))127         VALUE_PARSERS.put(IS_FIXED_PITCH, new WritingDirBooleanSetter(IS_FIXED_PITCH));
VALUE_PARSERS.put(C, new IntegerSetter(R))128         VALUE_PARSERS.put(C, new IntegerSetter("CharCode"));
VALUE_PARSERS.put(CH, new NotImplementedYet(CH))129         VALUE_PARSERS.put(CH, new NotImplementedYet(CH));
VALUE_PARSERS.put(WX, new DoubleSetter(R))130         VALUE_PARSERS.put(WX, new DoubleSetter("WidthX"));
VALUE_PARSERS.put(W0X, new DoubleSetter(R))131         VALUE_PARSERS.put(W0X, new DoubleSetter("WidthX"));
VALUE_PARSERS.put(W1X, new NotImplementedYet(W1X))132         VALUE_PARSERS.put(W1X, new NotImplementedYet(W1X));
VALUE_PARSERS.put(WY, new DoubleSetter(R))133         VALUE_PARSERS.put(WY, new DoubleSetter("WidthY"));
VALUE_PARSERS.put(W0Y, new DoubleSetter(R))134         VALUE_PARSERS.put(W0Y, new DoubleSetter("WidthY"));
VALUE_PARSERS.put(W1Y, new NotImplementedYet(W1Y))135         VALUE_PARSERS.put(W1Y, new NotImplementedYet(W1Y));
VALUE_PARSERS.put(W, new NotImplementedYet(W))136         VALUE_PARSERS.put(W, new NotImplementedYet(W));
VALUE_PARSERS.put(W0, new NotImplementedYet(W0))137         VALUE_PARSERS.put(W0, new NotImplementedYet(W0));
VALUE_PARSERS.put(W1, new NotImplementedYet(W1))138         VALUE_PARSERS.put(W1, new NotImplementedYet(W1));
VALUE_PARSERS.put(N, new NamedCharacterSetter(R))139         VALUE_PARSERS.put(N, new NamedCharacterSetter("Character"));
VALUE_PARSERS.put(B, new CharBBox())140         VALUE_PARSERS.put(B, new CharBBox());
VALUE_PARSERS.put(START_TRACK_KERN, new NotImplementedYet(START_TRACK_KERN))141         VALUE_PARSERS.put(START_TRACK_KERN, new NotImplementedYet(START_TRACK_KERN));
VALUE_PARSERS.put(START_KERN_PAIRS1, new NotImplementedYet(START_KERN_PAIRS1))142         VALUE_PARSERS.put(START_KERN_PAIRS1, new NotImplementedYet(START_KERN_PAIRS1));
VALUE_PARSERS.put(START_COMPOSITES, new NotImplementedYet(START_COMPOSITES))143         VALUE_PARSERS.put(START_COMPOSITES, new NotImplementedYet(START_COMPOSITES));
VALUE_PARSERS.put(START_COMP_FONT_METRICS, new NotImplementedYet(START_COMP_FONT_METRICS))144         VALUE_PARSERS.put(START_COMP_FONT_METRICS, new NotImplementedYet(START_COMP_FONT_METRICS));
VALUE_PARSERS.put(KP, new NotImplementedYet(KP))145         VALUE_PARSERS.put(KP, new NotImplementedYet(KP));
VALUE_PARSERS.put(KPH, new NotImplementedYet(KPH))146         VALUE_PARSERS.put(KPH, new NotImplementedYet(KPH));
VALUE_PARSERS.put(KPX, new KPXHandler())147         VALUE_PARSERS.put(KPX, new KPXHandler());
VALUE_PARSERS.put(KPY, new NotImplementedYet(KPY))148         VALUE_PARSERS.put(KPY, new NotImplementedYet(KPY));
149 
150         PARSE_MODE_CHANGES = new HashMap<String, Integer>();
PARSE_MODE_CHANGES.put(START_CHAR_METRICS, PARSE_CHAR_METRICS)151         PARSE_MODE_CHANGES.put(START_CHAR_METRICS, PARSE_CHAR_METRICS);
PARSE_MODE_CHANGES.put(END_CHAR_METRICS, PARSE_NORMAL)152         PARSE_MODE_CHANGES.put(END_CHAR_METRICS, PARSE_NORMAL);
153     }
154 
155     /**
156      * Main constructor.
157      */
AFMParser()158     public AFMParser() {
159     }
160 
161     /**
162      * Parses an AFM file from a stream.
163      * @param in the stream to read from
164      * @param afmFileName the name of the AFM file
165      * @return the parsed AFM file
166      * @throws IOException if an I/O error occurs
167      */
parse(InputStream in, String afmFileName)168     public AFMFile parse(InputStream in, String afmFileName) throws IOException {
169         Reader reader = new java.io.InputStreamReader(in, "US-ASCII");
170         try {
171             return parse(new BufferedReader(reader), afmFileName);
172         } finally {
173             IOUtils.closeQuietly(reader);
174         }
175     }
176 
177     /**
178      * Parses an AFM file from a BufferedReader.
179      * @param reader the BufferedReader instance to read from
180      * @param afmFileName the name of the AFM file
181      * @return the parsed AFM file
182      * @throws IOException if an I/O error occurs
183      */
parse(BufferedReader reader, String afmFileName)184     public AFMFile parse(BufferedReader reader, String afmFileName) throws IOException {
185         Stack<Object> stack = new Stack<Object>();
186         int parseMode = PARSE_NORMAL;
187         while (true) {
188             String line = reader.readLine();
189             if (line == null) {
190                 break;
191             }
192             String key = null;
193             switch (parseMode) {
194             case PARSE_NORMAL:
195                 key = parseLine(line, stack);
196                 break;
197             case PARSE_CHAR_METRICS:
198                 key = parseCharMetrics(line, stack, afmFileName);
199                 break;
200             default:
201                 throw new IllegalStateException("Invalid parse mode");
202             }
203             Integer newParseMode = PARSE_MODE_CHANGES.get(key);
204             if (newParseMode != null) {
205                 parseMode = newParseMode;
206             }
207         }
208         return (AFMFile)stack.pop();
209     }
210 
parseLine(String line, Stack<Object> stack)211     private String parseLine(String line, Stack<Object> stack) throws IOException {
212         int startpos = 0;
213         //Find key
214         startpos = skipToNonWhiteSpace(line, startpos);
215         int endpos = skipToWhiteSpace(line, startpos);
216         String key = line.substring(startpos, endpos);
217 
218         //Parse value
219         startpos = skipToNonWhiteSpace(line, endpos);
220         ValueHandler vp = VALUE_PARSERS.get(key);
221         if (vp != null) {
222             vp.parse(line, startpos, stack);
223         }
224         return key;
225     }
226 
parseCharMetrics(String line, Stack<Object> stack, String afmFileName)227     private String parseCharMetrics(String line, Stack<Object> stack, String afmFileName)
228             throws IOException {
229         String trimmedLine = line.trim();
230         if (END_CHAR_METRICS.equals(trimmedLine)) {
231             return trimmedLine;
232         }
233         AFMFile afm = (AFMFile) stack.peek();
234         String encoding = afm.getEncodingScheme();
235         CharMetricsHandler charMetricsHandler = CharMetricsHandler.getHandler(VALUE_PARSERS,
236                 encoding);
237         AFMCharMetrics chm = charMetricsHandler.parse(trimmedLine, stack, afmFileName);
238         afm.addCharMetrics(chm);
239         return null;
240     }
241 
skipToNonWhiteSpace(String line, int startpos)242     private static int skipToNonWhiteSpace(String line, int startpos) {
243         int pos = startpos;
244         while (pos < line.length() && isWhitespace(line.charAt(pos))) {
245             pos++;
246         }
247         return pos;
248     }
249 
skipToWhiteSpace(String line, int startpos)250     private static int skipToWhiteSpace(String line, int startpos) {
251         int pos = startpos;
252         while (pos < line.length() && !isWhitespace(line.charAt(pos))) {
253             pos++;
254         }
255         return pos;
256     }
257 
isWhitespace(char ch)258     private static boolean isWhitespace(char ch) {
259         return ch == ' '
260             || ch == '\t';
261     }
262 
263     // ---------------- Value Handlers ---------------------------
264 
265     interface ValueHandler {
parse(String line, int startpos, Stack<Object> stack)266         void parse(String line, int startpos, Stack<Object> stack) throws IOException;
267     }
268 
269     private abstract static class AbstractValueHandler implements ValueHandler {
270 
findValue(String line, int startpos)271         protected int findValue(String line, int startpos) {
272             return skipToWhiteSpace(line, startpos);
273         }
274 
getStringValue(String line, int startpos)275         protected String getStringValue(String line, int startpos) {
276             return line.substring(startpos);
277         }
278 
getNumberValue(String line, int startpos)279         protected Number getNumberValue(String line, int startpos) {
280             try {
281                 return getIntegerValue(line, startpos);
282             } catch (NumberFormatException nfe) {
283                 return getDoubleValue(line, startpos);
284             }
285         }
286 
getIntegerValue(String line, int startpos)287         protected int getIntegerValue(String line, int startpos) {
288             int endpos = findValue(line, startpos);
289             return Integer.parseInt(line.substring(startpos, endpos));
290         }
291 
getDoubleValue(String line, int startpos)292         protected double getDoubleValue(String line, int startpos) {
293             int endpos = findValue(line, startpos);
294             return Double.parseDouble(line.substring(startpos, endpos));
295         }
296 
getBooleanValue(String line, int startpos)297         protected Boolean getBooleanValue(String line, int startpos) {
298             return Boolean.valueOf(getStringValue(line, startpos));
299         }
300 
301     }
302 
303     private static class StartFontMetrics extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)304         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
305             int endpos = findValue(line, startpos);
306             double version = Double.parseDouble(line.substring(startpos, endpos));
307             if (version < 2) {
308                 throw new IOException(
309                         "AFM version must be at least 2.0 but it is " + version + "!");
310             }
311             AFMFile afm = new AFMFile();
312             stack.push(afm);
313         }
314     }
315 
316     private abstract static class BeanSetter extends AbstractValueHandler {
317         protected String method;
318 
BeanSetter(String variable)319         public BeanSetter(String variable) {
320             this.method = "set" + variable;
321         }
322 
setValue(Object target, Class<?> argType, Object value)323         protected void setValue(Object target, Class<?> argType, Object value) {
324             Class<?> c = target.getClass();
325 
326             try {
327                 Method mth = c.getMethod(method, argType);
328                 mth.invoke(target, value);
329             } catch (NoSuchMethodException e) {
330                 throw new RuntimeException("Bean error: " + e.getMessage(), e);
331             } catch (IllegalAccessException e) {
332                 throw new RuntimeException("Bean error: " + e.getMessage(), e);
333             } catch (InvocationTargetException e) {
334                 throw new RuntimeException("Bean error: " + e.getMessage(), e);
335             }
336         }
337     }
338 
339     private static class StringSetter extends BeanSetter {
340 
StringSetter(String variable)341         public StringSetter(String variable) {
342             super(variable);
343         }
344 
parse(String line, int startpos, Stack<Object> stack)345         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
346             String s = getStringValue(line, startpos);
347             Object obj = stack.peek();
348             setValue(obj, String.class, s);
349         }
350     }
351 
352     private static class NamedCharacterSetter extends BeanSetter {
353 
NamedCharacterSetter(String variable)354         public NamedCharacterSetter(String variable) {
355             super(variable);
356         }
357 
parse(String line, int startpos, Stack<Object> stack)358         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
359             NamedCharacter ch = new NamedCharacter(getStringValue(line, startpos));
360             Object obj = stack.peek();
361             setValue(obj, NamedCharacter.class, ch);
362         }
363     }
364 
365     private static class NumberSetter extends BeanSetter {
NumberSetter(String variable)366         public NumberSetter(String variable) {
367             super(variable);
368         }
369 
getContextObject(Stack<Object> stack)370         protected Object getContextObject(Stack<Object> stack) {
371             return stack.peek();
372         }
373 
parse(String line, int startpos, Stack<Object> stack)374         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
375             Number num = getNumberValue(line, startpos);
376             setValue(getContextObject(stack), Number.class, num);
377         }
378     }
379 
380     private static class IntegerSetter extends NumberSetter {
IntegerSetter(String variable)381         public IntegerSetter(String variable) {
382             super(variable);
383         }
384 
parse(String line, int startpos, Stack<Object> stack)385         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
386             int value = getIntegerValue(line, startpos);
387             setValue(getContextObject(stack), int.class, value);
388         }
389     }
390 
391     private static class DoubleSetter extends NumberSetter {
DoubleSetter(String variable)392         public DoubleSetter(String variable) {
393             super(variable);
394         }
395 
parse(String line, int startpos, Stack<Object> stack)396         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
397             double value = getDoubleValue(line, startpos);
398             setValue(getContextObject(stack), double.class, value);
399         }
400     }
401 
402     private static class WritingDirNumberSetter extends NumberSetter {
403 
WritingDirNumberSetter(String variable)404         public WritingDirNumberSetter(String variable) {
405             super(variable);
406         }
407 
getContextObject(Stack<Object> stack)408         protected Object getContextObject(Stack<Object> stack) {
409             if (stack.peek() instanceof AFMWritingDirectionMetrics) {
410                 return (AFMWritingDirectionMetrics)stack.peek();
411             } else {
412                 AFMFile afm = (AFMFile)stack.peek();
413                 AFMWritingDirectionMetrics wdm = afm.getWritingDirectionMetrics(0);
414                 if (wdm == null) {
415                     wdm = new AFMWritingDirectionMetrics();
416                     afm.setWritingDirectionMetrics(0, wdm);
417                 }
418                 return wdm;
419             }
420         }
421 
422     }
423 
424     private static class WritingDirDoubleSetter extends WritingDirNumberSetter {
425 
WritingDirDoubleSetter(String variable)426         public WritingDirDoubleSetter(String variable) {
427             super(variable);
428         }
429 
parse(String line, int startpos, Stack<Object> stack)430         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
431             double value = getDoubleValue(line, startpos);
432             setValue(getContextObject(stack), double.class, value);
433         }
434     }
435 
436     private static class BooleanSetter extends AbstractValueHandler {
437         private String method;
438 
BooleanSetter(String variable)439         public BooleanSetter(String variable) {
440             this.method = "set" + variable.substring(2); //Cut "Is" in front
441         }
442 
getContextObject(Stack<Object> stack)443         protected Object getContextObject(Stack<Object> stack) {
444             return (AFMFile)stack.peek();
445         }
446 
parse(String line, int startpos, Stack<Object> stack)447         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
448             Boolean b = getBooleanValue(line, startpos);
449 
450             Object target = getContextObject(stack);
451             Class<?> c = target.getClass();
452             try {
453                 Method mth = c.getMethod(method, boolean.class);
454                 mth.invoke(target, b);
455             } catch (NoSuchMethodException e) {
456                 throw new RuntimeException("Bean error: " + e.getMessage(), e);
457             } catch (IllegalAccessException e) {
458                 throw new RuntimeException("Bean error: " + e.getMessage(), e);
459             } catch (InvocationTargetException e) {
460                 throw new RuntimeException("Bean error: " + e.getMessage(), e);
461             }
462         }
463     }
464 
465     private static class WritingDirBooleanSetter extends BooleanSetter {
466 
WritingDirBooleanSetter(String variable)467         public WritingDirBooleanSetter(String variable) {
468             super(variable);
469         }
470 
getContextObject(Stack<Object> stack)471         protected Object getContextObject(Stack<Object> stack) {
472             if (stack.peek() instanceof AFMWritingDirectionMetrics) {
473                 return (AFMWritingDirectionMetrics)stack.peek();
474             } else {
475                 AFMFile afm = (AFMFile)stack.peek();
476                 AFMWritingDirectionMetrics wdm = afm.getWritingDirectionMetrics(0);
477                 if (wdm == null) {
478                     wdm = new AFMWritingDirectionMetrics();
479                     afm.setWritingDirectionMetrics(0, wdm);
480                 }
481                 return wdm;
482             }
483         }
484 
485     }
486 
487     private static class FontBBox extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)488         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
489             Rectangle rect = parseBBox(line, startpos);
490 
491             AFMFile afm = (AFMFile)stack.peek();
492             afm.setFontBBox(rect);
493         }
494 
parseBBox(String line, int startpos)495         protected Rectangle parseBBox(String line, int startpos) {
496             Rectangle rect = new Rectangle();
497             int endpos;
498 
499             endpos = findValue(line, startpos);
500             rect.x = Integer.parseInt(line.substring(startpos, endpos));
501             startpos = skipToNonWhiteSpace(line, endpos);
502 
503             endpos = findValue(line, startpos);
504             rect.y = Integer.parseInt(line.substring(startpos, endpos));
505             startpos = skipToNonWhiteSpace(line, endpos);
506 
507             endpos = findValue(line, startpos);
508             int v = Integer.parseInt(line.substring(startpos, endpos));
509             rect.width = v - rect.x;
510             startpos = skipToNonWhiteSpace(line, endpos);
511 
512             endpos = findValue(line, startpos);
513             v = Integer.parseInt(line.substring(startpos, endpos));
514             rect.height = v - rect.y;
515             startpos = skipToNonWhiteSpace(line, endpos);
516             return rect;
517         }
518     }
519 
520     private static class CharBBox extends FontBBox {
parse(String line, int startpos, Stack<Object> stack)521         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
522             Rectangle rect = parseBBox(line, startpos);
523 
524             AFMCharMetrics metrics = (AFMCharMetrics)stack.peek();
525             metrics.setBBox(rect);
526         }
527     }
528 
529     private static class IsBaseFont extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)530         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
531             if (getBooleanValue(line, startpos)) {
532                 throw new IOException("Only base fonts are currently supported!");
533             }
534         }
535     }
536 
537     private static class IsCIDFont extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)538         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
539             if (getBooleanValue(line, startpos)) {
540                 throw new IOException("CID fonts are currently not supported!");
541             }
542         }
543     }
544 
545     private static class NotImplementedYet extends AbstractValueHandler {
546         private String key;
547 
NotImplementedYet(String key)548         public NotImplementedYet(String key) {
549             this.key = key;
550         }
551 
parse(String line, int startpos, Stack stack)552         public void parse(String line, int startpos, Stack stack) throws IOException {
553             log.warn("Support for '" + key + "' has not been implemented, yet!"
554                     + " Some font data in the AFM file will be ignored.");
555         }
556     }
557 
558     private static class StartDirection extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)559         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
560             int index = getIntegerValue(line, startpos);
561             AFMWritingDirectionMetrics wdm = new AFMWritingDirectionMetrics();
562             AFMFile afm = (AFMFile)stack.peek();
563             afm.setWritingDirectionMetrics(index, wdm);
564             stack.push(wdm);
565         }
566     }
567 
568     private static class EndDirection extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)569         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
570             if (!(stack.pop() instanceof AFMWritingDirectionMetrics)) {
571                 throw new IOException("AFM format error: nesting incorrect");
572             }
573         }
574     }
575 
576     private static class KPXHandler extends AbstractValueHandler {
parse(String line, int startpos, Stack<Object> stack)577         public void parse(String line, int startpos, Stack<Object> stack) throws IOException {
578             AFMFile afm = (AFMFile)stack.peek();
579             int endpos;
580 
581             endpos = findValue(line, startpos);
582             String name1 = line.substring(startpos, endpos);
583             startpos = skipToNonWhiteSpace(line, endpos);
584 
585             endpos = findValue(line, startpos);
586             String name2 = line.substring(startpos, endpos);
587             startpos = skipToNonWhiteSpace(line, endpos);
588 
589             endpos = findValue(line, startpos);
590             double kx = Double.parseDouble(line.substring(startpos, endpos));
591             startpos = skipToNonWhiteSpace(line, endpos);
592 
593             afm.addXKerning(name1, name2, kx);
594         }
595     }
596 
597 }
598