Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

AFMParser.java 24KB

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