Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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