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 22KB

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