001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.bridge;
020:
021: import java.util.ArrayList;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.StringTokenizer;
027:
028: import org.apache.batik.dom.svg.SVGOMDocument;
029: import org.apache.batik.gvt.font.GVTFontFamily;
030: import org.apache.batik.gvt.font.GVTFontFace;
031: import org.apache.batik.gvt.font.UnresolvedFontFamily;
032: import org.apache.batik.util.SVGConstants;
033:
034: import org.apache.batik.css.engine.CSSEngine;
035: import org.apache.batik.css.engine.FontFaceRule;
036:
037: import org.w3c.dom.Document;
038: import org.w3c.dom.Element;
039: import org.w3c.dom.NodeList;
040:
041: /**
042: * Utility class for SVG fonts.
043: *
044: * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a>
045: * @version $Id: SVGFontUtilities.java 501844 2007-01-31 13:54:05Z dvholten $
046: */
047: public abstract class SVGFontUtilities implements SVGConstants {
048:
049: public static List getFontFaces(Document doc, BridgeContext ctx) {
050: // check fontFamilyMap to see if we have already created an
051: // FontFamily that matches
052: Map fontFamilyMap = ctx.getFontFamilyMap();
053: List ret = (List) fontFamilyMap.get(doc);
054: if (ret != null)
055: return ret;
056:
057: ret = new LinkedList();
058:
059: NodeList fontFaceElements = doc.getElementsByTagNameNS(
060: SVG_NAMESPACE_URI, SVG_FONT_FACE_TAG);
061:
062: SVGFontFaceElementBridge fontFaceBridge;
063: fontFaceBridge = (SVGFontFaceElementBridge) ctx.getBridge(
064: SVG_NAMESPACE_URI, SVG_FONT_FACE_TAG);
065:
066: for (int i = 0; i < fontFaceElements.getLength(); i++) {
067: Element fontFaceElement = (Element) fontFaceElements
068: .item(i);
069: ret
070: .add(fontFaceBridge.createFontFace(ctx,
071: fontFaceElement));
072: }
073:
074: CSSEngine engine = ((SVGOMDocument) doc).getCSSEngine();
075: List sms = engine.getFontFaces();
076: Iterator iter = sms.iterator();
077: while (iter.hasNext()) {
078: FontFaceRule ffr = (FontFaceRule) iter.next();
079: ret.add(CSSFontFace.createCSSFontFace(engine, ffr));
080: }
081: return ret;
082: }
083:
084: /**
085: * Given a font family name tries to find a matching SVG font
086: * object. If finds one, returns an SVGFontFamily otherwise
087: * returns an UnresolvedFontFamily.
088: *
089: * @param textElement The text element that the font family will
090: * be attached to.
091: * @param ctx The bridge context, used to search for a matching
092: * SVG font element.
093: * @param fontFamilyName The name of the font family to search
094: * for.
095: * @param fontWeight The weight of the font to use when trying to
096: * match an SVG font family.
097: * @param fontStyle The style of the font to use when trying to
098: * match as SVG font family.
099: *
100: * @return A GVTFontFamily for the specified font attributes. This
101: * will be unresolved unless a matching SVG font was found.
102: */
103: public static GVTFontFamily getFontFamily(Element textElement,
104: BridgeContext ctx, String fontFamilyName,
105: String fontWeight, String fontStyle) {
106:
107: // TODO: should match against font-variant as well
108: String fontKeyName = fontFamilyName.toLowerCase() + " " + // todo locale??
109: fontWeight + " " + fontStyle;
110:
111: // check fontFamilyMap to see if we have already created an
112: // FontFamily that matches
113: Map fontFamilyMap = ctx.getFontFamilyMap();
114: GVTFontFamily fontFamily = (GVTFontFamily) fontFamilyMap
115: .get(fontKeyName);
116: if (fontFamily != null) {
117: return fontFamily;
118: }
119:
120: // try to find a matching SVGFontFace element
121: Document doc = textElement.getOwnerDocument();
122:
123: List fontFaces = (List) fontFamilyMap.get(doc);
124:
125: if (fontFaces == null) {
126: fontFaces = getFontFaces(doc, ctx);
127: fontFamilyMap.put(doc, fontFaces);
128: }
129:
130: Iterator iter = fontFaces.iterator();
131: List svgFontFamilies = new LinkedList();
132: while (iter.hasNext()) {
133: FontFace fontFace = (FontFace) iter.next();
134:
135: if (!fontFace.hasFamilyName(fontFamilyName)) {
136: continue;
137: }
138:
139: String fontFaceStyle = fontFace.getFontStyle();
140: if (fontFaceStyle.equals(SVG_ALL_VALUE)
141: || fontFaceStyle.indexOf(fontStyle) != -1) {
142: GVTFontFamily ffam = fontFace.getFontFamily(ctx);
143: if (ffam != null)
144: svgFontFamilies.add(ffam);
145: }
146: }
147:
148: if (svgFontFamilies.size() == 1) {
149: // only found one matching svg font family
150: fontFamilyMap.put(fontKeyName, svgFontFamilies.get(0));
151: return (GVTFontFamily) svgFontFamilies.get(0);
152:
153: } else if (svgFontFamilies.size() > 1) {
154: // need to find font face that matches the font-weight closest
155: String fontWeightNumber = getFontWeightNumberString(fontWeight);
156:
157: // create lists of font weight numbers for each font family
158: List fontFamilyWeights = new ArrayList(svgFontFamilies
159: .size());
160: Iterator ffiter = svgFontFamilies.iterator();
161: while (ffiter.hasNext()) {
162: GVTFontFace fontFace;
163: fontFace = ((GVTFontFamily) ffiter.next())
164: .getFontFace();
165: String fontFaceWeight = fontFace.getFontWeight();
166: fontFaceWeight = getFontWeightNumberString(fontFaceWeight);
167: fontFamilyWeights.add(fontFaceWeight);
168: }
169:
170: // make sure that each possible font-weight has been
171: // assigned to a font-face, if not then need to "fill the
172: // holes"
173:
174: List newFontFamilyWeights = new ArrayList(fontFamilyWeights);
175: for (int i = 100; i <= 900; i += 100) {
176: String weightString = String.valueOf(i);
177: boolean matched = false;
178: int minDifference = 1000;
179: int minDifferenceIndex = 0;
180: for (int j = 0; j < fontFamilyWeights.size(); j++) {
181: String fontFamilyWeight = (String) fontFamilyWeights
182: .get(j);
183: if (fontFamilyWeight.indexOf(weightString) > -1) {
184: matched = true;
185: break;
186: }
187: StringTokenizer st = new StringTokenizer(
188: fontFamilyWeight, " ,");
189: while (st.hasMoreTokens()) {
190: int weightNum = Integer
191: .parseInt(st.nextToken());
192: int difference = Math.abs(weightNum - i);
193: if (difference < minDifference) {
194: minDifference = difference;
195: minDifferenceIndex = j;
196: }
197: }
198: }
199: if (!matched) {
200: String newFontFamilyWeight = newFontFamilyWeights
201: .get(minDifferenceIndex)
202: + ", " + weightString;
203: newFontFamilyWeights.set(minDifferenceIndex,
204: newFontFamilyWeight);
205: }
206: }
207:
208: // now find matching font weight
209: for (int i = 0; i < svgFontFamilies.size(); i++) {
210: String fontFaceWeight = (String) newFontFamilyWeights
211: .get(i);
212: if (fontFaceWeight.indexOf(fontWeightNumber) > -1) {
213: fontFamilyMap.put(fontKeyName, svgFontFamilies
214: .get(i));
215: return (GVTFontFamily) svgFontFamilies.get(i);
216: }
217: }
218: // should not get here, just return the first svg font family
219: fontFamilyMap.put(fontKeyName, svgFontFamilies.get(0));
220: return (GVTFontFamily) svgFontFamilies.get(0);
221:
222: } else {
223: // couldn't find one so return an UnresolvedFontFamily object
224: GVTFontFamily gvtFontFamily = new UnresolvedFontFamily(
225: fontFamilyName);
226: fontFamilyMap.put(fontKeyName, gvtFontFamily);
227: return gvtFontFamily;
228: }
229: }
230:
231: /**
232: * Returns a string that contains all of the font weight numbers for the
233: * specified font weight attribute value.
234: *
235: * @param fontWeight The font-weight attribute value.
236: *
237: * @return The font weight expressed as font weight numbers.
238: * e.g. "normal" becomes "400".
239: */
240: protected static String getFontWeightNumberString(String fontWeight) {
241: if (fontWeight.equals(SVG_NORMAL_VALUE)) {
242: return SVG_400_VALUE;
243: } else if (fontWeight.equals(SVG_BOLD_VALUE)) {
244: return SVG_700_VALUE;
245: } else if (fontWeight.equals(SVG_ALL_VALUE)) {
246: return "100, 200, 300, 400, 500, 600, 700, 800, 900";
247: }
248: return fontWeight;
249: }
250: }
|