View Javadoc

1   // Copyright 2001-2006, FreeHEP.
2   package org.freehep.graphicsio.svg;
3   
4   import java.awt.Font;
5   import java.awt.font.FontRenderContext;
6   import java.awt.font.GlyphVector;
7   import java.awt.font.TextAttribute;
8   import java.awt.font.TextLayout;
9   import java.awt.geom.AffineTransform;
10  import java.util.Enumeration;
11  import java.util.Hashtable;
12  import java.util.Iterator;
13  import java.util.Map;
14  import java.util.Properties;
15  
16  import org.freehep.graphics2d.font.FontUtilities;
17  import org.freehep.graphicsio.font.FontTable;
18  
19  /**
20   * A table to remember which glyphs were used while writing a svg file.
21   * Entries are added by calling {@link #addGlyphs(String, java.awt.Font)}.
22   * The final SVG tag for the <defs> section is generated using {@link #toString()}.
23   * Use {@link #normalize(java.util.Map)} for referencing embedded glyphs
24   * in <text> tags.
25   *
26   * @author Steffen Greiffenberg
27   * @version $Id: SVGFontTable.java 12753 2007-06-12 22:32:31Z duns $
28   */
29  public class SVGFontTable {
30  
31      /**
32       * Stores fonts and a glyph-hashtable. The font key ist normalized using
33       * {@link #untransform(java.awt.Font)}
34       */
35      private Hashtable/*<Font, Hashtable<String, SVGGlyph>*/ glyphs =
36          new Hashtable/*<Font, Hashtable<String SVGGlyph>>*/();
37  
38      /**
39       * creates a glyph for the string character
40       *
41       * @param c
42       * @param font
43       * @return unique font name
44       */
45      private SVGGlyph addGlyph(int c, Font font) {
46          // is the font stored?
47          Hashtable/*<String, SVGGlyph>*/ glyphs = getGlyphs(font);
48  
49          // does a glyph allready exist?
50          SVGGlyph result = (SVGGlyph) glyphs.get(String.valueOf(c));
51  
52          // create a new one?
53          if (result == null) {
54              // create and store the SVG Glyph
55              result = createGlyph(c, font);
56              glyphs.put(String.valueOf(c), result);
57          }
58  
59          return result;
60      }
61  
62      /**
63       * @param c
64       * @param font
65       * @return GlyphVector using a default rendering context
66       */
67      private SVGGlyph createGlyph(int c, Font font) {
68          GlyphVector glyphVector = font.createGlyphVector(
69              // flipping is done by SVGGlyph
70              new FontRenderContext(null, true, true),
71              // unicode to char
72              String.valueOf((char) c));
73  
74          // create and store the SVG Glyph
75          return new SVGGlyph(
76              glyphVector.getGlyphOutline(0),
77              c,
78              glyphVector.getGlyphMetrics(0));
79      }
80  
81      /**
82       * creates the glyph for the string
83       *
84       * @param string
85       * @param font
86       */
87      protected void addGlyphs(String string, Font font) {
88          font = untransform(font);
89  
90          // add characters
91          for (int i = 0; i < string.length(); i ++) {
92              addGlyph(string.charAt(i), font);
93          }
94      }
95  
96      /**
97       * @param font
98       * @return glyph vectors for font
99       */
100     private Hashtable/*<String SVGGlyph>*/ getGlyphs(Font font) {
101         // derive a default font for the font table
102         font = untransform(font);
103 
104         Hashtable/*<String SVGGlyph>*/ result =
105             (Hashtable/*<String SVGGlyph>*/) glyphs.get(font);
106         if (result == null) {
107             result = new Hashtable/*<String SVGGlyph>*/();
108             glyphs.put(font, result);
109         }
110         return result;
111     }
112 
113     /**
114      * creates the font entry:
115      * <PRE>
116      * <font>
117      * <glyph ... />
118      * ...
119      * </font>
120      * </PRE>
121      *
122      * @return string representing the entry
123      */
124     public String toString() {
125         StringBuffer result = new StringBuffer();
126 
127         Enumeration/*<Font>*/ fonts = this.glyphs.keys();
128         while (fonts.hasMoreElements()) {
129             Font font = (Font) fonts.nextElement();
130 
131             // replace font family for svg
132             Map /*<TextAttribute, ?>*/ attributes = FontUtilities.getAttributes(font);
133 
134             // Dialog -> Helvetica
135             normalize(attributes);
136 
137             // familiy
138             result.append("<font id=\"");
139             result.append(attributes.get(TextAttribute.FAMILY));
140             result.append("\">\n");
141 
142             // font-face
143             result.append("<font-face font-family=\"");
144             result.append(attributes.get(TextAttribute.FAMILY));
145             result.append("\" ");
146 
147             // bold
148             if (TextAttribute.WEIGHT_BOLD.equals(attributes.get(TextAttribute.WEIGHT))) {
149                 result.append("font-weight=\"bold\" ");
150             } else {
151                 result.append("font-weight=\"normal\" ");
152             }
153 
154             // italic
155             if (TextAttribute.POSTURE_OBLIQUE.equals(attributes.get(TextAttribute.POSTURE))) {
156                 result.append("font-style=\"italic\" ");
157             } else {
158                 result.append("font-style=\"normal\" ");
159             }
160 
161             // size
162             Float size = (Float) attributes.get(TextAttribute.SIZE);
163             result.append("font-size=\"");
164             result.append(SVGGraphics2D.fixedPrecision(size.floatValue()));
165             result.append("\" ");
166 
167             // number of coordinate units on the em square,
168             // the size of the design grid on which glyphs are laid out
169             result.append("units-per-em=\"");
170             result.append(SVGGraphics2D.fixedPrecision(SVGGlyph.FONT_SIZE));
171             result.append("\" ");
172 
173             TextLayout tl = new TextLayout("By", font, new FontRenderContext(new AffineTransform(), true, true));
174 
175             // The maximum unaccented height of the font within the font coordinate system.
176             // If the attribute is not specified, the effect is as if the attribute were set
177             // to the difference between the units-per-em value and the vert-origin-y value
178             // for the corresponding font.
179             result.append("ascent=\"");
180             result.append(tl.getAscent());
181             result.append("\" ");
182 
183             // The maximum unaccented depth of the font within the font coordinate system.
184             // If the attribute is not specified, the effect is as if the attribute were set
185             // to the vert-origin-y value for the corresponding font.
186             result.append("desscent=\"");
187             result.append(tl.getDescent());
188             result.append("\" ");
189 
190             // For horizontally oriented glyph layouts, indicates the alignment
191             // coordinate for glyphs to achieve alphabetic baseline alignment.
192             // result.append("alphabetic=\"0\"
193 
194             // close "<font-face"
195             result.append("/>\n");
196 
197             // missing glyph
198             SVGGlyph glyph = createGlyph(font.getMissingGlyphCode(), font);
199             result.append("<missing-glyph ");
200             result.append(glyph.getHorizontalAdvanceXString());
201             result.append(" ");
202             result.append(glyph.getPathString());
203             result.append("/>\n");
204 
205             // regular glyphs
206             Iterator glyphs = getGlyphs(font).values().iterator();
207             while (glyphs.hasNext()) {
208                 result.append(glyphs.next().toString());
209                 result.append("\n");
210             }
211 
212             // close "<font>"
213             result.append("</font>\n");
214         }
215 
216         return result.toString();
217     }
218 
219     /**
220      * creates a font based on the parameter. The size will be {@link SVGGlyph#FONT_SIZE}
221      * and transformation will be removed. Example:<BR>
222      * <code>java.awt.Font[family=SansSerif,name=SansSerif,style=plain,size=30]</code><BR>
223      * will result to:<BR>
224      * <code>java.awt.Font[family=SansSerif,name=SansSerif,style=plain,size=100]</code><BR><BR>
225      *
226      * This method does not substitute font name or family.
227      *
228      * @param font
229      * @return font based on the parameter
230      */
231     private Font untransform(Font font) {
232         // replace font family
233         Map /*<TextAttribute, ?>*/ attributes = FontUtilities.getAttributes(font);
234 
235         // set default font size
236         attributes.put(TextAttribute.SIZE, new Float(SVGGlyph.FONT_SIZE));
237 
238         // remove font transformation
239         attributes.remove(TextAttribute.TRANSFORM);
240         attributes.remove(TextAttribute.SUPERSCRIPT);
241 
242         return new Font(attributes);
243     }
244 
245     /**
246      * font replacements makes SVG in AdobeViewer look better, firefox replaces
247      * all font settings, even the family fame
248      */
249     private static final Properties replaceFonts = new Properties();
250     static {
251         replaceFonts.setProperty("dialog", "Helvetica");
252         replaceFonts.setProperty("dialoginput", "Courier New");
253         // FIXME: works well on windows, others?
254         // "TimesRoman" is not valid under Firefox 1.5
255         replaceFonts.setProperty("serif", "Times");
256         replaceFonts.setProperty("timesroman", "Times");
257         replaceFonts.setProperty("sansserif", "Helvetica");
258         // FIXME: works well on windows, others?
259         // "Courier" is not valid under Firefox 1.5
260         replaceFonts.setProperty("monospaced", "Courier New");
261         // FIXME: replacement for zapfdingbats?
262         replaceFonts.setProperty("zapfdingbats", "Wingdings");
263     }
264 
265     /**
266      * Replaces TextAttribute.FAMILY by values of replaceFonts. When a
267      * font created using the result of this method the transformation would be:
268      *
269      * <code>java.awt.Font[family=SansSerif,name=SansSerif,style=plain,size=30]</code><BR>
270      * will result to:<BR>
271      * <code>java.awt.Font[family=SansSerif,name=Helvetica,style=plain,size=30]</code><BR><BR>
272      *
273      * Uses {@link FontTable#normalize(java.util.Map)} first.
274      *
275      * @param attributes with font name to change
276      */
277     public static void normalize(Map /*<TextAttribute, ?>*/ attributes) {
278         // dialog.bold -> Dialog with TextAttribute.WEIGHT_BOLD
279         FontTable.normalize(attributes);
280 
281         // get replaced font family name (Yes it's right, not the name!)
282         String family = replaceFonts.getProperty(
283             ((String) attributes.get(TextAttribute.FAMILY)).toLowerCase());
284         if (family == null) {
285             family = (String) attributes.get(TextAttribute.FAMILY);
286         }
287         
288         // replace the family (Yes it's right, not the name!) in the attributes
289         attributes.put(TextAttribute.FAMILY, family);
290     }
291 }