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