KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > svggen > SVGFont


1 /*
2
3    Copyright 2001-2003 The Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    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  */

18 package org.apache.batik.svggen;
19
20 import java.awt.Font JavaDoc;
21 import java.awt.Shape JavaDoc;
22 import java.awt.font.FontRenderContext JavaDoc;
23 import java.awt.font.GlyphMetrics JavaDoc;
24 import java.awt.font.GlyphVector JavaDoc;
25 import java.awt.font.LineMetrics JavaDoc;
26 import java.awt.font.TextAttribute JavaDoc;
27 import java.awt.geom.AffineTransform JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.Map JavaDoc;
30
31 import org.apache.batik.ext.awt.g2d.GraphicContext;
32 import org.w3c.dom.Document JavaDoc;
33 import org.w3c.dom.Element JavaDoc;
34 import org.w3c.dom.NodeList JavaDoc;
35
36 /**
37  * Utility class that converts a Font object into a set of SVG
38  * font attributes
39  *
40  * @author <a HREF="mailto:cjolif@ilog.fr">Christophe Jolif</a>
41  * @author <a HREF="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
42  * @version $Id: SVGFont.java,v 1.19 2004/08/18 07:15:00 vhardy Exp $
43  */

44 public class SVGFont extends AbstractSVGConverter {
45     public static final float EXTRA_LIGHT =
46         TextAttribute.WEIGHT_EXTRA_LIGHT.floatValue();
47     public static final float LIGHT =
48         TextAttribute.WEIGHT_LIGHT.floatValue();
49     public static final float DEMILIGHT =
50         TextAttribute.WEIGHT_DEMILIGHT.floatValue();
51     public static final float REGULAR =
52         TextAttribute.WEIGHT_REGULAR.floatValue();
53     public static final float SEMIBOLD =
54         TextAttribute.WEIGHT_SEMIBOLD.floatValue();
55     public static final float MEDIUM =
56         TextAttribute.WEIGHT_MEDIUM.floatValue();
57     public static final float DEMIBOLD =
58         TextAttribute.WEIGHT_DEMIBOLD.floatValue();
59     public static final float BOLD =
60         TextAttribute.WEIGHT_BOLD.floatValue();
61     public static final float HEAVY =
62         TextAttribute.WEIGHT_HEAVY.floatValue();
63     public static final float EXTRABOLD =
64         TextAttribute.WEIGHT_EXTRABOLD.floatValue();
65     public static final float ULTRABOLD =
66         TextAttribute.WEIGHT_ULTRABOLD.floatValue();
67
68     public static final float POSTURE_REGULAR =
69         TextAttribute.POSTURE_REGULAR.floatValue();
70     public static final float POSTURE_OBLIQUE =
71         TextAttribute.POSTURE_OBLIQUE.floatValue();
72
73     /**
74      * Contains threshold value for the various Font styles. If a given
75      * style is in an interval, then it is mapped to the style at the top
76      * of that interval.
77      * @see #styleToSVG
78      */

79     static final float fontStyles[] = {
80         POSTURE_REGULAR + (POSTURE_OBLIQUE - POSTURE_REGULAR)/2
81     };
82
83     /**
84      * SVG Styles corresponding to the fontStyles
85      */

86     static final String JavaDoc svgStyles[] = {
87         /*POSTURE_REGULAR*/ SVG_NORMAL_VALUE,
88         /*POSTURE_OBLIQUE*/ SVG_ITALIC_VALUE
89     };
90
91     /**
92      * Contains threshold values for the various Font weights. If a given
93      * weight is in an interval, then it is mapped to the weight at the top
94      * of the interval.
95      * @see #weightToSVG
96      */

97     static final float fontWeights[] = { EXTRA_LIGHT + (LIGHT - EXTRA_LIGHT)/2f,
98                                          LIGHT + (DEMILIGHT - LIGHT)/2f,
99                                          DEMILIGHT + (REGULAR - DEMILIGHT)/2f,
100                                          REGULAR + (SEMIBOLD - REGULAR)/2f,
101                                          SEMIBOLD + (MEDIUM - SEMIBOLD)/2f,
102                                          MEDIUM + (DEMIBOLD - MEDIUM)/2f,
103                                          DEMIBOLD + (BOLD - DEMIBOLD)/2f,
104                                          BOLD + (HEAVY - BOLD)/2f,
105                                          HEAVY + (EXTRABOLD - HEAVY)/2f,
106                                          EXTRABOLD + (ULTRABOLD - EXTRABOLD),
107     };
108
109     /**
110      * SVG Weights corresponding to the fontWeights
111      */

112     static final String JavaDoc svgWeights[] = {
113         /*EXTRA_LIGHT*/ SVG_100_VALUE,
114         /*LIGHT*/ SVG_200_VALUE,
115         /*DEMILIGHT*/ SVG_300_VALUE,
116         /*REGULAR*/ SVG_NORMAL_VALUE,
117         /*SEMIBOLD*/ SVG_500_VALUE,
118         /*MEDIUM*/ SVG_500_VALUE,
119         /*DEMIBOLD*/ SVG_600_VALUE,
120         /*BOLD*/ SVG_BOLD_VALUE,
121         /*HEAVY*/ SVG_800_VALUE,
122         /*EXTRABOLD*/ SVG_800_VALUE,
123         /*ULTRABOLD*/ SVG_900_VALUE
124     };
125
126     /**
127      * Logical fonts mapping
128      */

129     static Map JavaDoc logicalFontMap = new HashMap JavaDoc();
130
131     static {
132         logicalFontMap.put("dialog", "sans-serif");
133         logicalFontMap.put("dialoginput", "monospace");
134         logicalFontMap.put("monospaced", "monospace");
135         logicalFontMap.put("serif", "serif");
136         logicalFontMap.put("sansserif", "sans-serif");
137         logicalFontMap.put("symbol", "'WingDings'");
138     }
139
140     /**
141      * The common font size to use when generating all SVG fonts.
142      */

143     static final int COMMON_FONT_SIZE = 100;
144
145     /**
146      * Used to keep track of which characters have been rendered by each font
147      * used.
148      */

149     Map JavaDoc fontStringMap = new HashMap JavaDoc();
150
151     /**
152      * @param generatorContext used to build Elements
153      */

154     public SVGFont(SVGGeneratorContext generatorContext) {
155         super(generatorContext);
156     }
157
158     /**
159      * Records that the specified font has been used to draw the text string.
160      * This is so we can keep track of which glyphs are required for each
161      * SVG font that is generated.
162      */

163     public void recordFontUsage(String JavaDoc string, Font JavaDoc font) {
164
165         Font JavaDoc commonSizeFont = createCommonSizeFont(font);
166         String JavaDoc fontKey = commonSizeFont.getFamily() + commonSizeFont.getStyle();
167         String JavaDoc textUsingFont = (String JavaDoc)fontStringMap.get(fontKey);
168         if (textUsingFont == null) {
169             // font has not been used before
170
textUsingFont = "";
171         }
172         // append any new characters to textUsingFont
173
char ch;
174         for (int i = 0; i < string.length(); i++) {
175             ch = string.charAt(i);
176             if (textUsingFont.indexOf(ch) == -1) {
177                 textUsingFont += ch;
178             }
179         }
180         fontStringMap.put(fontKey, textUsingFont);
181     }
182
183     /**
184      * Creates a new Font that is of the common font size used for generating
185      * SVG fonts. The new Font will be the same as the specified font, with
186      * only its size attribute modified.
187      */

188     private static Font JavaDoc createCommonSizeFont(Font JavaDoc font) {
189         HashMap JavaDoc attributes = new HashMap JavaDoc(font.getAttributes());
190         attributes.put(TextAttribute.SIZE, new Float JavaDoc(COMMON_FONT_SIZE));
191         return new Font JavaDoc(attributes);
192     }
193
194     /**
195      * Converts part or all of the input GraphicContext into
196      * a set of attribute/value pairs and related definitions
197      *
198      * @param gc GraphicContext to be converted
199      * @return descriptor of the attributes required to represent
200      * some or all of the GraphicContext state, along
201      * with the related definitions
202      * @see org.apache.batik.svggen.SVGDescriptor
203      */

204     public SVGDescriptor toSVG(GraphicContext gc) {
205         return toSVG(gc.getFont(), gc.getFontRenderContext());
206     }
207
208     /**
209      * @param font Font object which should be converted to a set
210      * of SVG attributes
211      * @param frc The FontRenderContext which will be used to generate glyph
212      * elements for the SVGFont definition element
213      * @return description of attribute values that describe the font
214      */

215     public SVGFontDescriptor toSVG(Font JavaDoc font, FontRenderContext JavaDoc frc) {
216
217         String JavaDoc fontSize = "" + doubleString(font.getSize2D());
218         String JavaDoc fontWeight = weightToSVG(font);
219         String JavaDoc fontStyle = styleToSVG(font);
220         String JavaDoc fontFamilyStr = familyToSVG(font);
221
222         Font JavaDoc commonSizeFont = createCommonSizeFont(font);
223         String JavaDoc fontKey = commonSizeFont.getFamily() + commonSizeFont.getStyle();
224
225         String JavaDoc textUsingFont = (String JavaDoc)fontStringMap.get(fontKey);
226
227         if (textUsingFont == null) {
228             // this font hasn't been used by any text yet,
229
// so don't create an SVG Font element for it
230
return new SVGFontDescriptor(fontSize, fontWeight,
231                                          fontStyle, fontFamilyStr,
232                                          null);
233         }
234
235         Document JavaDoc domFactory = generatorContext.domFactory;
236
237         // see if a description already exists for this font
238
SVGFontDescriptor fontDesc =
239             (SVGFontDescriptor)descMap.get(fontKey);
240
241         Element JavaDoc fontDef;
242
243         if (fontDesc != null) {
244
245             // use the SVG Font element that has already been created
246
fontDef = fontDesc.getDef();
247
248         } else {
249
250             // create a new SVG Font element
251
fontDef = domFactory.createElementNS(SVG_NAMESPACE_URI,
252                                                  SVG_FONT_TAG);
253
254             //
255
// create the font-face element
256
//
257
Element JavaDoc fontFace = domFactory.createElementNS(SVG_NAMESPACE_URI,
258                                                           SVG_FONT_FACE_TAG);
259             String JavaDoc svgFontFamilyString = fontFamilyStr;
260             if (fontFamilyStr.startsWith("'") && fontFamilyStr.endsWith("'")) {
261                 // get rid of the quotes
262
svgFontFamilyString
263                     = fontFamilyStr.substring(1, fontFamilyStr.length()-1);
264             }
265             fontFace.setAttributeNS(null, SVG_FONT_FAMILY_ATTRIBUTE,
266                                     svgFontFamilyString);
267             fontFace.setAttributeNS(null, SVG_FONT_WEIGHT_ATTRIBUTE,
268                                     fontWeight);
269             fontFace.setAttributeNS(null, SVG_FONT_STYLE_ATTRIBUTE,
270                                     fontStyle);
271             fontFace.setAttributeNS(null, SVG_UNITS_PER_EM_ATTRIBUTE,
272                                     ""+COMMON_FONT_SIZE);
273             fontDef.appendChild(fontFace);
274
275             //
276
// create missing glyph element
277
//
278
Element JavaDoc missingGlyphElement
279                 = domFactory.createElementNS(SVG_NAMESPACE_URI,
280                                              SVG_MISSING_GLYPH_TAG);
281
282             int missingGlyphCode[] = new int[1];
283             missingGlyphCode[0] = commonSizeFont.getMissingGlyphCode();
284             GlyphVector JavaDoc gv = commonSizeFont.createGlyphVector(frc, missingGlyphCode);
285             Shape JavaDoc missingGlyphShape = gv.getGlyphOutline(0);
286             GlyphMetrics JavaDoc gm = gv.getGlyphMetrics(0);
287
288             // need to turn the missing glyph upside down to be in the font
289
// coordinate system (i.e Y axis up)
290
AffineTransform JavaDoc at = AffineTransform.getScaleInstance(1, -1);
291             missingGlyphShape = at.createTransformedShape(missingGlyphShape);
292
293             missingGlyphElement.setAttributeNS(null, SVG_D_ATTRIBUTE,
294                                     SVGPath.toSVGPathData(missingGlyphShape, generatorContext));
295             missingGlyphElement.setAttributeNS(null, SVG_HORIZ_ADV_X_ATTRIBUTE,
296                                                "" + gm.getAdvance());
297             fontDef.appendChild(missingGlyphElement);
298
299             // set the font's default horizontal advance to be the same as
300
// the missing glyph
301
fontDef.setAttributeNS(null, SVG_HORIZ_ADV_X_ATTRIBUTE, "" + gm.getAdvance());
302
303             // set the ascent and descent attributes
304
LineMetrics JavaDoc lm = commonSizeFont.getLineMetrics("By", frc);
305             fontFace.setAttributeNS(null, SVG_ASCENT_ATTRIBUTE, "" + lm.getAscent());
306             fontFace.setAttributeNS(null, SVG_DESCENT_ATTRIBUTE, "" + lm.getDescent());
307
308             //
309
// Font ID
310
//
311
fontDef.setAttributeNS(null, ATTR_ID,
312                                    generatorContext.idGenerator.
313                                    generateID(ID_PREFIX_FONT));
314         }
315
316         //
317
// add any new glyphs to the fontDef here
318
//
319

320         // process the characters in textUsingFont backwards since the new chars
321
// are at the end, can stop when find a char that already has a glyph
322
for (int i = textUsingFont.length()-1; i >= 0; i--) {
323             char c = textUsingFont.charAt(i);
324             boolean foundGlyph = false;
325             NodeList JavaDoc fontChildren = fontDef.getChildNodes();
326             for (int j = 0; j < fontChildren.getLength(); j++) {
327                 if (fontChildren.item(j) instanceof Element JavaDoc) {
328                     Element JavaDoc childElement = (Element JavaDoc)fontChildren.item(j);
329                     if (childElement.getAttributeNS(null,
330                             SVG_UNICODE_ATTRIBUTE).equals(""+c)) {
331                         foundGlyph = true;
332                         break;
333                     }
334                 }
335             }
336             if (!foundGlyph) {
337                 // need to create one
338
Element JavaDoc glyphElement
339                     = domFactory.createElementNS(SVG_NAMESPACE_URI,
340                                                  SVG_GLYPH_TAG);
341
342                 GlyphVector JavaDoc gv = commonSizeFont.createGlyphVector(frc, ""+c);
343                 Shape JavaDoc glyphShape = gv.getGlyphOutline(0);
344                 GlyphMetrics JavaDoc gm = gv.getGlyphMetrics(0);
345
346                 // need to turn the glyph upside down to be in the font
347
// coordinate system (i.e Y axis up)
348
AffineTransform JavaDoc at = AffineTransform.getScaleInstance(1, -1);
349                 glyphShape = at.createTransformedShape(glyphShape);
350
351                 glyphElement.setAttributeNS(null, SVG_D_ATTRIBUTE,
352                                             SVGPath.toSVGPathData(glyphShape, generatorContext));
353                 glyphElement.setAttributeNS(null, SVG_HORIZ_ADV_X_ATTRIBUTE,
354                                             "" + gm.getAdvance());
355                 glyphElement.setAttributeNS(null, SVG_UNICODE_ATTRIBUTE,
356                                             "" + c);
357
358                 fontDef.appendChild(glyphElement);
359             } else {
360                 // have reached the chars in textUsingFont that already
361
// have glyphs, don't need to process any further
362
break;
363             }
364         }
365
366         //
367
// create a new font description for this instance of the font usage
368
//
369
SVGFontDescriptor newFontDesc
370             = new SVGFontDescriptor(fontSize, fontWeight,
371                                     fontStyle, fontFamilyStr,
372                                     fontDef);
373
374         //
375
// Update maps so that the font def can be reused if needed
376
//
377
if (fontDesc == null) {
378             descMap.put(fontKey, newFontDesc);
379             defSet.add(fontDef);
380         }
381
382         return newFontDesc;
383     }
384
385     /**
386      * @param font whose family should be converted to an SVG string
387      * value.
388      */

389     public static String JavaDoc familyToSVG(Font JavaDoc font) {
390         String JavaDoc fontFamilyStr = font.getFamily();
391         String JavaDoc logicalFontFamily =
392             (String JavaDoc)logicalFontMap.get(font.getName().toLowerCase());
393         if (logicalFontFamily != null)
394             fontFamilyStr = logicalFontFamily;
395         else {
396             StringBuffer JavaDoc fontFamily = new StringBuffer JavaDoc("'");
397             fontFamily.append(fontFamilyStr);
398             fontFamily.append("'");
399             fontFamilyStr = fontFamily.toString();
400         }
401         return fontFamilyStr;
402     }
403
404     /**
405      * @param font whose style should be converted to an SVG string
406      * value.
407      */

408     public static String JavaDoc styleToSVG(Font JavaDoc font) {
409         Map JavaDoc attrMap = font.getAttributes();
410         Float JavaDoc styleValue = (Float JavaDoc)attrMap.get(TextAttribute.POSTURE);
411
412         if (styleValue == null) {
413             if (font.isItalic())
414                 styleValue = TextAttribute.POSTURE_OBLIQUE;
415             else
416                 styleValue = TextAttribute.POSTURE_REGULAR;
417         }
418
419         float style = styleValue.floatValue();
420
421         int i = 0;
422         for (i=0; i< fontStyles.length; i++) {
423             if (style <= fontStyles[i])
424                 break;
425         }
426
427         return svgStyles[i];
428     }
429
430     /**
431      * @param font whose weight should be converted to an SVG string
432      * value. Note that there is loss of precision for
433      * semibold and extrabold.
434      */

435     public static String JavaDoc weightToSVG(Font JavaDoc font) {
436         Map JavaDoc attrMap = font.getAttributes();
437         Float JavaDoc weightValue = (Float JavaDoc)attrMap.get(TextAttribute.WEIGHT);
438         if (weightValue==null) {
439             if (font.isBold())
440                 weightValue = TextAttribute.WEIGHT_BOLD;
441             else
442                 weightValue = TextAttribute.WEIGHT_REGULAR;
443         }
444
445         float weight = weightValue.floatValue();
446
447         int i = 0;
448         for (i=0; i<fontWeights.length; i++) {
449             if (weight<=fontWeights[i])
450                 break;
451         }
452
453         return svgWeights[i];
454     }
455 }
456
Popular Tags