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

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