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.

FontInfo.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  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;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.SortedSet;
  25. import java.util.TreeSet;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. /**
  29. * The FontInfo holds font information for the layout and rendering of a fo document.
  30. * This stores the list of available fonts that are setup by
  31. * the renderer. The font name can be retrieved for the
  32. * family style and weight.
  33. * <br>
  34. * Currently font supported font-variant small-caps is not
  35. * implemented.
  36. */
  37. public class FontInfo {
  38. /** logging instance */
  39. protected static final Log log = LogFactory.getLog(FontInfo.class);
  40. /** Map containing fonts that have been used */
  41. private Map<String, Typeface> usedFonts = null; //(String = font key)
  42. /** look up a font-triplet to find a font-name */
  43. private Map<FontTriplet, String> triplets = null; //(String = font key)
  44. /** look up a font-triplet to find its priority
  45. * (only used inside addFontProperties()) */
  46. private Map<FontTriplet, Integer> tripletPriorities = null; //Map<FontTriplet,Integer>
  47. /** look up a font-name to get a font (that implements FontMetrics at least) */
  48. private Map<String, Typeface> fonts = null; //(String = font key)
  49. /** Cache for Font instances. */
  50. private Map<FontTriplet, Map<Integer, Font>> fontInstanceCache = null;
  51. /** Event listener for font events */
  52. private FontEventListener eventListener = null;
  53. /**
  54. * Main constructor
  55. */
  56. public FontInfo() {
  57. this.triplets = new HashMap<FontTriplet, String>();
  58. this.tripletPriorities = new HashMap<FontTriplet, Integer>();
  59. this.fonts = new HashMap<String, Typeface>();
  60. this.usedFonts = new HashMap<String, Typeface>();
  61. }
  62. /**
  63. * Sets the font event listener that can be used to receive events about particular events
  64. * in this class.
  65. * @param listener the font event listener
  66. */
  67. public void setEventListener(FontEventListener listener) {
  68. this.eventListener = listener;
  69. }
  70. /**
  71. * Checks if the font setup is valid (At least the ultimate fallback font
  72. * must be registered.)
  73. * @return True if valid
  74. */
  75. public boolean isSetupValid() {
  76. //We're only called when font setup is done:
  77. tripletPriorities = null; // candidate for garbage collection
  78. return triplets.containsKey(Font.DEFAULT_FONT);
  79. }
  80. /**
  81. * Adds a new font triplet.
  82. * @param name internal key
  83. * @param family font family name
  84. * @param style font style (normal, italic, oblique...)
  85. * @param weight font weight
  86. */
  87. public void addFontProperties(String name, String family, String style, int weight) {
  88. addFontProperties(name, createFontKey(family, style, weight));
  89. }
  90. /**
  91. * Adds a series of new font triplets given an array of font family names.
  92. * @param name internal key
  93. * @param families an array of font family names
  94. * @param style font style (normal, italic, oblique...)
  95. * @param weight font weight
  96. */
  97. public void addFontProperties(String name, String[] families, String style, int weight) {
  98. for (int i = 0; i < families.length; i++) {
  99. addFontProperties(name, families[i], style, weight);
  100. }
  101. }
  102. /**
  103. * Adds a new font triplet.
  104. * @param internalFontKey internal font key
  105. * @param triplet the font triplet to associate with the internal key
  106. */
  107. public void addFontProperties(String internalFontKey, FontTriplet triplet) {
  108. /*
  109. * add the given family, style and weight as a lookup for the font
  110. * with the given name
  111. */
  112. if (log.isDebugEnabled()) {
  113. log.debug("Registering: " + triplet + " under " + internalFontKey);
  114. }
  115. String oldName = triplets.get(triplet);
  116. int newPriority = triplet.getPriority();
  117. if (oldName != null) {
  118. int oldPriority = tripletPriorities.get(triplet).intValue();
  119. if (oldPriority < newPriority) {
  120. logDuplicateFont(triplet, false, oldName, oldPriority,
  121. internalFontKey, newPriority);
  122. return;
  123. } else {
  124. logDuplicateFont(triplet, true, oldName, oldPriority,
  125. internalFontKey, newPriority);
  126. }
  127. }
  128. this.triplets.put(triplet, internalFontKey);
  129. this.tripletPriorities.put(triplet, Integer.valueOf(newPriority));
  130. }
  131. /**
  132. * Log warning about duplicate font triplets.
  133. *
  134. * @param triplet the duplicate font triplet
  135. * @param replacing true iff the new font will replace the old one
  136. * @param oldKey the old internal font name
  137. * @param oldPriority the priority of the existing font mapping
  138. * @param newKey the new internal font name
  139. * @param newPriority the priority of the duplicate font mapping
  140. */
  141. private void logDuplicateFont(FontTriplet triplet, boolean replacing,
  142. String oldKey, int oldPriority,
  143. String newKey, int newPriority) {
  144. if (log.isDebugEnabled()) {
  145. log.debug(triplet
  146. + (replacing ? ": Replacing " : ": Not replacing ")
  147. + fonts.get(triplets.get(triplet)).getFullName()
  148. + " (priority=" + oldPriority + ") by "
  149. + fonts.get(newKey).getFullName()
  150. + " (priority=" + newPriority + ")");
  151. }
  152. }
  153. /**
  154. * Adds font metrics for a specific font.
  155. * @param internalFontKey internal key
  156. * @param metrics metrics to register
  157. */
  158. public void addMetrics(String internalFontKey, FontMetrics metrics) {
  159. // add the given metrics as a font with the given name
  160. if (metrics instanceof Typeface) {
  161. ((Typeface)metrics).setEventListener(this.eventListener);
  162. }
  163. this.fonts.put(internalFontKey, (Typeface)metrics);
  164. }
  165. /**
  166. * Lookup a font.
  167. * <br>
  168. * Locate the font name for a given family, style and weight.
  169. * The font name can then be used as a key as it is unique for
  170. * the associated document.
  171. * This also adds the font to the list of used fonts.
  172. * @param family font family
  173. * @param style font style
  174. * @param weight font weight
  175. * @param substitutable true if the font may be substituted with the
  176. * default font if not found
  177. * @return internal font triplet key
  178. */
  179. private FontTriplet fontLookup(String family, String style,
  180. int weight, boolean substitutable) {
  181. if (log.isTraceEnabled()) {
  182. log.trace("Font lookup: " + family + " " + style + " " + weight
  183. + (substitutable ? " substitutable" : ""));
  184. }
  185. FontTriplet startKey = createFontKey(family, style, weight);
  186. FontTriplet fontTriplet = startKey;
  187. // first try given parameters
  188. String internalFontKey = getInternalFontKey(fontTriplet);
  189. if (internalFontKey == null) {
  190. fontTriplet = fuzzyFontLookup(family, style, weight, startKey, substitutable);
  191. }
  192. if (fontTriplet != null) {
  193. if (fontTriplet != startKey) {
  194. notifyFontReplacement(startKey, fontTriplet);
  195. }
  196. return fontTriplet;
  197. } else {
  198. return null;
  199. }
  200. }
  201. private FontTriplet fuzzyFontLookup(String family, String style,
  202. int weight, FontTriplet startKey, boolean substitutable) {
  203. FontTriplet key;
  204. String internalFontKey = null;
  205. if (!family.equals(startKey.getName())) {
  206. key = createFontKey(family, style, weight);
  207. internalFontKey = getInternalFontKey(key);
  208. if (internalFontKey != null) {
  209. return key;
  210. }
  211. }
  212. // adjust weight, favouring normal or bold
  213. key = findAdjustWeight(family, style, weight);
  214. if (key != null) {
  215. internalFontKey = getInternalFontKey(key);
  216. }
  217. // return null if not found and not substitutable
  218. if (!substitutable && internalFontKey == null) {
  219. return null;
  220. }
  221. // only if the font may be substituted
  222. // fallback 1: try the same font-family and weight with default style
  223. if (internalFontKey == null && style != Font.STYLE_NORMAL) {
  224. key = createFontKey(family, Font.STYLE_NORMAL, weight);
  225. internalFontKey = getInternalFontKey(key);
  226. }
  227. // fallback 2: try the same font-family with default style and try to adjust weight
  228. if (internalFontKey == null && style != Font.STYLE_NORMAL) {
  229. key = findAdjustWeight(family, Font.STYLE_NORMAL, weight);
  230. if (key != null) {
  231. internalFontKey = getInternalFontKey(key);
  232. }
  233. }
  234. // fallback 3: try any family with original style/weight
  235. if (internalFontKey == null) {
  236. return fuzzyFontLookup("any", style, weight, startKey, false);
  237. }
  238. // last resort: use default
  239. if (key == null && internalFontKey == null) {
  240. key = Font.DEFAULT_FONT;
  241. internalFontKey = getInternalFontKey(key);
  242. }
  243. if (internalFontKey != null) {
  244. return key;
  245. } else {
  246. return null;
  247. }
  248. }
  249. /**
  250. * Tells this class that the font with the given internal name has been used.
  251. * @param internalName the internal font name (F1, F2 etc.)
  252. */
  253. public void useFont(String internalName) {
  254. usedFonts.put(internalName, fonts.get(internalName));
  255. }
  256. private Map<FontTriplet, Map<Integer, Font>> getFontInstanceCache() {
  257. if (fontInstanceCache == null) {
  258. fontInstanceCache = new HashMap<FontTriplet, Map<Integer, Font>>();
  259. }
  260. return fontInstanceCache;
  261. }
  262. /**
  263. * Retrieves a (possibly cached) Font instance based on a FontTriplet and a font size.
  264. *
  265. * @param triplet the font triplet designating the requested font
  266. * @param fontSize the font size
  267. * @return the requested Font instance
  268. */
  269. public Font getFontInstance(FontTriplet triplet, int fontSize) {
  270. Map<Integer, Font> sizes
  271. = getFontInstanceCache().get(triplet);
  272. if (sizes == null) {
  273. sizes = new HashMap<Integer, Font>();
  274. getFontInstanceCache().put(triplet, sizes);
  275. }
  276. Integer size = Integer.valueOf(fontSize);
  277. Font font = sizes.get(size);
  278. if (font == null) {
  279. String fontKey = getInternalFontKey(triplet);
  280. useFont(fontKey);
  281. FontMetrics metrics = getMetricsFor(fontKey);
  282. font = new Font(fontKey, triplet, metrics, fontSize);
  283. sizes.put(size, font);
  284. }
  285. return font;
  286. }
  287. private List<FontTriplet> getTripletsForName(String fontName) {
  288. List<FontTriplet> matchedTriplets = new ArrayList<FontTriplet>();
  289. for (FontTriplet triplet : triplets.keySet()) {
  290. String tripletName = triplet.getName();
  291. if (tripletName.toLowerCase().equals(fontName.toLowerCase())) {
  292. matchedTriplets.add(triplet);
  293. }
  294. }
  295. return matchedTriplets;
  296. }
  297. /**
  298. * Returns a suitable internal font given an AWT Font instance.
  299. *
  300. * @param awtFont the AWT font
  301. * @return a best matching internal Font
  302. */
  303. public Font getFontInstanceForAWTFont(java.awt.Font awtFont) {
  304. String awtFontName = awtFont.getName();
  305. String awtFontFamily = awtFont.getFamily();
  306. String awtFontStyle = awtFont.isItalic() ? Font.STYLE_ITALIC : Font.STYLE_NORMAL;
  307. int awtFontWeight = awtFont.isBold() ? Font.WEIGHT_BOLD : Font.WEIGHT_NORMAL;
  308. FontTriplet matchedTriplet = null;
  309. List<FontTriplet> triplets = getTripletsForName(awtFontName);
  310. if (!triplets.isEmpty()) {
  311. for (FontTriplet triplet : triplets) {
  312. boolean styleMatched = triplet.getStyle().equals(awtFontStyle);
  313. boolean weightMatched = triplet.getWeight() == awtFontWeight;
  314. if (styleMatched && weightMatched) {
  315. matchedTriplet = triplet;
  316. break;
  317. }
  318. }
  319. }
  320. // not matched on font name so do a lookup using family
  321. if (matchedTriplet == null) {
  322. if (awtFontFamily.equals("sanserif")) {
  323. awtFontFamily = "sans-serif";
  324. }
  325. matchedTriplet = fontLookup(awtFontFamily, awtFontStyle, awtFontWeight);
  326. }
  327. int fontSize = Math.round(awtFont.getSize2D() * 1000);
  328. return getFontInstance(matchedTriplet, fontSize);
  329. }
  330. /**
  331. * Lookup a font.
  332. * <br>
  333. * Locate the font name for a given family, style and weight.
  334. * The font name can then be used as a key as it is unique for
  335. * the associated document.
  336. * This also adds the font to the list of used fonts.
  337. * @param family font family
  338. * @param style font style
  339. * @param weight font weight
  340. * @return the font triplet of the font chosen
  341. */
  342. public FontTriplet fontLookup(String family, String style,
  343. int weight) {
  344. return fontLookup(family, style, weight, true);
  345. }
  346. private List<FontTriplet> fontLookup(String[] families, String style,
  347. int weight, boolean substitutable) {
  348. List<FontTriplet> matchingTriplets = new ArrayList<FontTriplet>();
  349. FontTriplet triplet = null;
  350. for (int i = 0; i < families.length; i++) {
  351. triplet = fontLookup(families[i], style, weight, substitutable);
  352. if (triplet != null) {
  353. matchingTriplets.add(triplet);
  354. }
  355. }
  356. return matchingTriplets;
  357. }
  358. /**
  359. * Looks up a set of fonts.
  360. * <br>
  361. * Locate the font name(s) for the given families, style and weight.
  362. * The font name(s) can then be used as a key as they are unique for
  363. * the associated document.
  364. * This also adds the fonts to the list of used fonts.
  365. * @param families font families (priority list)
  366. * @param style font style
  367. * @param weight font weight
  368. * @return the set of font triplets of all supported and chosen font-families
  369. * in the specified style and weight.
  370. */
  371. public FontTriplet[] fontLookup(String[] families, String style,
  372. int weight) {
  373. if (families.length == 0) {
  374. throw new IllegalArgumentException("Specify at least one font family");
  375. }
  376. // try matching without substitutions
  377. List<FontTriplet> matchedTriplets = fontLookup(families, style, weight, false);
  378. // if there are no matching font triplets found try with substitutions
  379. if (matchedTriplets.size() == 0) {
  380. matchedTriplets = fontLookup(families, style, weight, true);
  381. }
  382. // no matching font triplets found!
  383. if (matchedTriplets.size() == 0) {
  384. StringBuffer sb = new StringBuffer();
  385. for (int i = 0, c = families.length; i < c; i++) {
  386. if (i > 0) {
  387. sb.append(", ");
  388. }
  389. sb.append(families[i]);
  390. }
  391. throw new IllegalStateException(
  392. "fontLookup must return an array with at least one "
  393. + "FontTriplet on the last call. Lookup: " + sb.toString());
  394. }
  395. FontTriplet[] fontTriplets = new FontTriplet[matchedTriplets.size()];
  396. matchedTriplets.toArray(fontTriplets);
  397. // found some matching fonts so return them
  398. return fontTriplets;
  399. }
  400. private void notifyFontReplacement(FontTriplet replacedKey, FontTriplet newKey) {
  401. if (this.eventListener != null) {
  402. this.eventListener.fontSubstituted(this, replacedKey, newKey);
  403. }
  404. }
  405. /**
  406. * Notify listeners that the SVG text for the given font will be stroked as shapes.
  407. * @param fontFamily a SVG font family
  408. */
  409. public void notifyStrokingSVGTextAsShapes(String fontFamily) {
  410. if (this.eventListener != null) {
  411. this.eventListener.svgTextStrokedAsShapes(this, fontFamily);
  412. }
  413. }
  414. /**
  415. * Find a font with a given family and style by trying
  416. * different font weights according to the spec.
  417. * @param family font family
  418. * @param style font style
  419. * @param weight font weight
  420. * @return internal key
  421. */
  422. public FontTriplet findAdjustWeight(String family, String style,
  423. int weight) {
  424. FontTriplet key = null;
  425. String f = null;
  426. int newWeight = weight;
  427. if (newWeight < 400) {
  428. while (f == null && newWeight > 100) {
  429. newWeight -= 100;
  430. key = createFontKey(family, style, newWeight);
  431. f = getInternalFontKey(key);
  432. }
  433. newWeight = weight;
  434. while (f == null && newWeight < 400) {
  435. newWeight += 100;
  436. key = createFontKey(family, style, newWeight);
  437. f = getInternalFontKey(key);
  438. }
  439. } else if (newWeight == 400 || newWeight == 500) {
  440. key = createFontKey(family, style, 400);
  441. f = getInternalFontKey(key);
  442. } else if (newWeight > 500) {
  443. while (f == null && newWeight < 1000) {
  444. newWeight += 100;
  445. key = createFontKey(family, style, newWeight);
  446. f = getInternalFontKey(key);
  447. }
  448. newWeight = weight;
  449. while (f == null && newWeight > 400) {
  450. newWeight -= 100;
  451. key = createFontKey(family, style, newWeight);
  452. f = getInternalFontKey(key);
  453. }
  454. }
  455. if (f == null && weight != 400) {
  456. key = createFontKey(family, style, 400);
  457. f = getInternalFontKey(key);
  458. }
  459. if (f != null) {
  460. return key;
  461. } else {
  462. return null;
  463. }
  464. }
  465. /**
  466. * Determines if a particular font is available.
  467. * @param family font family
  468. * @param style font style
  469. * @param weight font weight
  470. * @return True if available
  471. */
  472. public boolean hasFont(String family, String style, int weight) {
  473. FontTriplet key = createFontKey(family, style, weight);
  474. return this.triplets.containsKey(key);
  475. }
  476. /**
  477. * Returns the internal font key (F1, F2, F3 etc.) for a given triplet.
  478. * @param triplet the font triplet
  479. * @return the associated internal key or null, if not found
  480. */
  481. public String getInternalFontKey(FontTriplet triplet) {
  482. return triplets.get(triplet);
  483. }
  484. /**
  485. * Creates a key from the given strings.
  486. * @param family font family
  487. * @param style font style
  488. * @param weight font weight
  489. * @return internal key
  490. */
  491. public static FontTriplet createFontKey(String family, String style,
  492. int weight) {
  493. return new FontTriplet(family, style, weight);
  494. }
  495. /**
  496. * Gets a Map of all registered fonts.
  497. * @return a read-only Map with font key/FontMetrics pairs
  498. */
  499. public Map<String, Typeface> getFonts() {
  500. return Collections.unmodifiableMap(this.fonts);
  501. }
  502. /**
  503. * Gets a Map of all registered font triplets.
  504. * @return a Map with FontTriplet/font key pairs
  505. */
  506. public Map<FontTriplet, String> getFontTriplets() {
  507. return this.triplets;
  508. }
  509. /**
  510. * This is used by the renderers to retrieve all the
  511. * fonts used in the document.
  512. * This is for embedded font or creating a list of used fonts.
  513. * @return a read-only Map with font key/FontMetrics pairs
  514. */
  515. public Map<String, Typeface> getUsedFonts() {
  516. return this.usedFonts;
  517. }
  518. /**
  519. * Returns the FontMetrics for a particular font
  520. * @param fontName internal key
  521. * @return font metrics
  522. */
  523. public FontMetrics getMetricsFor(String fontName) {
  524. Typeface metrics = fonts.get(fontName);
  525. usedFonts.put(fontName, metrics);
  526. return metrics;
  527. }
  528. /**
  529. * Returns all font triplet matching the given font name.
  530. * @param fontName The font name we are looking for
  531. * @return A list of matching font triplets
  532. */
  533. public List<FontTriplet> getTripletsFor(String fontName) {
  534. List<FontTriplet> foundTriplets = new ArrayList<FontTriplet> ();
  535. for (Map.Entry<FontTriplet, String> tripletEntry : triplets.entrySet()) {
  536. if (fontName.equals((tripletEntry.getValue()))) {
  537. foundTriplets.add(tripletEntry.getKey());
  538. }
  539. }
  540. return foundTriplets;
  541. }
  542. /**
  543. * Returns the first triplet matching the given font name.
  544. * As there may be multiple triplets matching the font name
  545. * the result set is sorted first to guarantee consistent results.
  546. * @param fontName The font name we are looking for
  547. * @return The first triplet for the given font name
  548. */
  549. public FontTriplet getTripletFor(String fontName) {
  550. List<FontTriplet> foundTriplets = getTripletsFor(fontName);
  551. if (foundTriplets.size() > 0) {
  552. Collections.sort(foundTriplets);
  553. return foundTriplets.get(0);
  554. }
  555. return null;
  556. }
  557. /**
  558. * Returns the font style for a particular font.
  559. * There may be multiple font styles matching this font. Only the first
  560. * found is returned. Searching is done on a sorted list to guarantee consistent
  561. * results.
  562. * @param fontName internal key
  563. * @return font style
  564. */
  565. public String getFontStyleFor(String fontName) {
  566. FontTriplet triplet = getTripletFor(fontName);
  567. if (triplet != null) {
  568. return triplet.getStyle();
  569. } else {
  570. return "";
  571. }
  572. }
  573. /**
  574. * Returns the font weight for a particular font.
  575. * There may be multiple font weights matching this font. Only the first
  576. * found is returned. Searching is done on a sorted list to guarantee consistent
  577. * results.
  578. * @param fontName internal key
  579. * @return font weight
  580. */
  581. public int getFontWeightFor(String fontName) {
  582. FontTriplet triplet = getTripletFor(fontName);
  583. if (triplet != null) {
  584. return triplet.getWeight();
  585. } else {
  586. return 0;
  587. }
  588. }
  589. /**
  590. * Diagnostic method for logging all registered fonts to System.out.
  591. */
  592. public void dumpAllTripletsToSystemOut() {
  593. System.out.print(toString());
  594. }
  595. /**
  596. * {@inheritDoc}
  597. */
  598. @Override
  599. public String toString() {
  600. SortedSet<String> entries = new TreeSet<String>();
  601. for (FontTriplet triplet : this.triplets.keySet()) {
  602. String key = getInternalFontKey(triplet);
  603. FontMetrics metrics = getMetricsFor(key);
  604. entries.add(triplet.toString() + " -> " + key + " -> " + metrics.getFontName() + "\n");
  605. }
  606. StringBuffer stringBuffer = new StringBuffer();
  607. for (String str : entries) {
  608. stringBuffer.append(str);
  609. }
  610. return stringBuffer.toString();
  611. }
  612. }