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 }