You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AFMParser.java 23KB

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