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.awt.Shape;
022: import java.awt.geom.AffineTransform;
023: import java.awt.geom.Point2D;
024: import java.util.StringTokenizer;
025: import java.util.List;
026: import java.util.ArrayList;
027:
028: import org.apache.batik.gvt.CompositeGraphicsNode;
029: import org.apache.batik.gvt.GraphicsNode;
030: import org.apache.batik.gvt.font.GVTFontFace;
031: import org.apache.batik.gvt.font.Glyph;
032: import org.apache.batik.gvt.text.TextPaintInfo;
033: import org.apache.batik.parser.AWTPathProducer;
034: import org.apache.batik.parser.ParseException;
035: import org.apache.batik.parser.PathParser;
036:
037: import org.w3c.dom.Attr;
038: import org.w3c.dom.Element;
039: import org.w3c.dom.NamedNodeMap;
040: import org.w3c.dom.Node;
041: import org.w3c.dom.NodeList;
042:
043: /**
044: * Bridge class for the <glyph> element.
045: *
046: * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a>
047: * @version $Id: SVGGlyphElementBridge.java 501922 2007-01-31 17:47:47Z dvholten $
048: */
049: public class SVGGlyphElementBridge extends AbstractSVGBridge implements
050: ErrorConstants {
051:
052: /**
053: * Constructs a new bridge for the <glyph> element.
054: */
055: protected SVGGlyphElementBridge() {
056: }
057:
058: /**
059: * Returns 'glyph'.
060: */
061: public String getLocalName() {
062: return SVG_GLYPH_TAG;
063: }
064:
065: /**
066: * Constructs a new Glyph that represents the specified <glyph> element
067: * at the requested size.
068: *
069: * @param ctx The current bridge context.
070: * @param glyphElement The glyph element to base the glyph construction on.
071: * @param textElement The textElement the glyph will be used for.
072: * @param glyphCode The unique id to give to the new glyph.
073: * @param fontSize The font size used to determine the size of the glyph.
074: * @param fontFace The font face object that contains the font attributes.
075: *
076: * @return The new Glyph.
077: */
078: public Glyph createGlyph(BridgeContext ctx, Element glyphElement,
079: Element textElement, int glyphCode, float fontSize,
080: GVTFontFace fontFace, TextPaintInfo tpi) {
081:
082: float fontHeight = fontFace.getUnitsPerEm();
083: float scale = fontSize / fontHeight;
084: AffineTransform scaleTransform = AffineTransform
085: .getScaleInstance(scale, -scale);
086:
087: // create a shape that represents the d attribute
088: String d = glyphElement.getAttributeNS(null, SVG_D_ATTRIBUTE);
089: Shape dShape = null;
090: if (d.length() != 0) {
091: AWTPathProducer app = new AWTPathProducer();
092: // Glyph is supposed to use properties from text element.
093: app.setWindingRule(CSSUtilities
094: .convertFillRule(textElement));
095: try {
096: PathParser pathParser = new PathParser();
097: pathParser.setPathHandler(app);
098: pathParser.parse(d);
099: } catch (ParseException pEx) {
100: throw new BridgeException(ctx, glyphElement, pEx,
101: ERR_ATTRIBUTE_VALUE_MALFORMED,
102: new Object[] { SVG_D_ATTRIBUTE });
103: } finally {
104: // transform the shape into the correct coord system
105: Shape shape = app.getShape();
106: Shape transformedShape = scaleTransform
107: .createTransformedShape(shape);
108: dShape = transformedShape;
109: }
110: }
111:
112: // process any glyph children
113:
114: // first see if there are any, because don't want to do the following
115: // bit of code if we can avoid it
116:
117: NodeList glyphChildren = glyphElement.getChildNodes();
118: int numChildren = glyphChildren.getLength();
119: int numGlyphChildren = 0;
120: for (int i = 0; i < numChildren; i++) {
121: Node childNode = glyphChildren.item(i);
122: if (childNode.getNodeType() == Node.ELEMENT_NODE) {
123: numGlyphChildren++;
124: }
125: }
126:
127: CompositeGraphicsNode glyphContentNode = null;
128:
129: if (numGlyphChildren > 0) { // the glyph has child elements
130:
131: // build the GVT tree that represents the glyph children
132: GVTBuilder builder = ctx.getGVTBuilder();
133:
134: glyphContentNode = new CompositeGraphicsNode();
135:
136: //
137: // need to clone the parent font element and glyph element
138: // this is so that the glyph doesn't inherit anything past the font element
139: //
140: Element fontElementClone = (Element) glyphElement
141: .getParentNode().cloneNode(false);
142:
143: // copy all font attributes over
144: NamedNodeMap fontAttributes = glyphElement.getParentNode()
145: .getAttributes();
146:
147: int numAttributes = fontAttributes.getLength();
148: for (int i = 0; i < numAttributes; i++) {
149: fontElementClone.setAttributeNode((Attr) fontAttributes
150: .item(i));
151: }
152: Element clonedGlyphElement = (Element) glyphElement
153: .cloneNode(true);
154: fontElementClone.appendChild(clonedGlyphElement);
155:
156: textElement.appendChild(fontElementClone);
157:
158: CompositeGraphicsNode glyphChildrenNode = new CompositeGraphicsNode();
159:
160: glyphChildrenNode.setTransform(scaleTransform);
161:
162: NodeList clonedGlyphChildren = clonedGlyphElement
163: .getChildNodes();
164: int numClonedChildren = clonedGlyphChildren.getLength();
165: for (int i = 0; i < numClonedChildren; i++) {
166: Node childNode = clonedGlyphChildren.item(i);
167: if (childNode.getNodeType() == Node.ELEMENT_NODE) {
168: Element childElement = (Element) childNode;
169: GraphicsNode childGraphicsNode = builder.build(ctx,
170: childElement);
171: glyphChildrenNode.add(childGraphicsNode);
172: }
173: }
174: glyphContentNode.add(glyphChildrenNode);
175: textElement.removeChild(fontElementClone);
176: }
177:
178: // set up glyph attributes
179:
180: // unicode
181: String unicode = glyphElement.getAttributeNS(null,
182: SVG_UNICODE_ATTRIBUTE);
183:
184: // glyph-name
185: String nameList = glyphElement.getAttributeNS(null,
186: SVG_GLYPH_NAME_ATTRIBUTE);
187: List names = new ArrayList();
188: StringTokenizer st = new StringTokenizer(nameList, " ,");
189: while (st.hasMoreTokens()) {
190: names.add(st.nextToken());
191: }
192:
193: // orientation
194: String orientation = glyphElement.getAttributeNS(null,
195: SVG_ORIENTATION_ATTRIBUTE);
196:
197: // arabicForm
198: String arabicForm = glyphElement.getAttributeNS(null,
199: SVG_ARABIC_FORM_ATTRIBUTE);
200:
201: // lang
202: String lang = glyphElement.getAttributeNS(null,
203: SVG_LANG_ATTRIBUTE);
204:
205: Element parentFontElement = (Element) glyphElement
206: .getParentNode();
207:
208: // horz-adv-x
209: String s = glyphElement.getAttributeNS(null,
210: SVG_HORIZ_ADV_X_ATTRIBUTE);
211: if (s.length() == 0) {
212: // look for attribute on parent font element
213: s = parentFontElement.getAttributeNS(null,
214: SVG_HORIZ_ADV_X_ATTRIBUTE);
215: if (s.length() == 0) {
216: // throw an exception since this attribute is required on the font element
217: throw new BridgeException(ctx, parentFontElement,
218: ERR_ATTRIBUTE_MISSING,
219: new Object[] { SVG_HORIZ_ADV_X_ATTRIBUTE });
220: }
221: }
222: float horizAdvX;
223: try {
224: horizAdvX = SVGUtilities.convertSVGNumber(s) * scale;
225: } catch (NumberFormatException nfEx) {
226: throw new BridgeException(ctx, glyphElement, nfEx,
227: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
228: SVG_HORIZ_ADV_X_ATTRIBUTE, s });
229: }
230:
231: // vert-adv-y
232: s = glyphElement.getAttributeNS(null, SVG_VERT_ADV_Y_ATTRIBUTE);
233: if (s.length() == 0) {
234: // look for attribute on parent font element
235: s = parentFontElement.getAttributeNS(null,
236: SVG_VERT_ADV_Y_ATTRIBUTE);
237: if (s.length() == 0) {
238: // not specified on parent either, use one em
239: s = String.valueOf(fontFace.getUnitsPerEm());
240: }
241: }
242: float vertAdvY;
243: try {
244: vertAdvY = SVGUtilities.convertSVGNumber(s) * scale;
245: } catch (NumberFormatException nfEx) {
246: throw new BridgeException(ctx, glyphElement, nfEx,
247: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
248: SVG_VERT_ADV_Y_ATTRIBUTE, s });
249: }
250:
251: // vert-origin-x
252: s = glyphElement.getAttributeNS(null,
253: SVG_VERT_ORIGIN_X_ATTRIBUTE);
254: if (s.length() == 0) {
255: // look for attribute on parent font element
256: s = parentFontElement.getAttributeNS(null,
257: SVG_VERT_ORIGIN_X_ATTRIBUTE);
258: if (s.length() == 0) {
259: // not specified so use the default value which is horizAdvX/2
260: s = Float.toString(horizAdvX / 2);
261: }
262: }
263: float vertOriginX;
264: try {
265: vertOriginX = SVGUtilities.convertSVGNumber(s) * scale;
266: } catch (NumberFormatException nfEx) {
267: throw new BridgeException(ctx, glyphElement, nfEx,
268: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
269: SVG_VERT_ORIGIN_X_ATTRIBUTE, s });
270: }
271:
272: // vert-origin-y
273: s = glyphElement.getAttributeNS(null,
274: SVG_VERT_ORIGIN_Y_ATTRIBUTE);
275: if (s.length() == 0) {
276: // look for attribute on parent font element
277: s = parentFontElement.getAttributeNS(null,
278: SVG_VERT_ORIGIN_Y_ATTRIBUTE);
279: if (s.length() == 0) {
280: // not specified so use the default value which is the fonts ascent
281: s = String.valueOf(fontFace.getAscent());
282: }
283: }
284: float vertOriginY;
285: try {
286: vertOriginY = SVGUtilities.convertSVGNumber(s) * -scale;
287: } catch (NumberFormatException nfEx) {
288: throw new BridgeException(ctx, glyphElement, nfEx,
289: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
290: SVG_VERT_ORIGIN_Y_ATTRIBUTE, s });
291: }
292:
293: Point2D vertOrigin = new Point2D.Float(vertOriginX, vertOriginY);
294:
295: // get the horizontal origin from the parent font element
296:
297: // horiz-origin-x
298: s = parentFontElement.getAttributeNS(null,
299: SVG_HORIZ_ORIGIN_X_ATTRIBUTE);
300: if (s.length() == 0) {
301: // not specified so use the default value which is 0
302: s = SVG_HORIZ_ORIGIN_X_DEFAULT_VALUE;
303: }
304: float horizOriginX;
305: try {
306: horizOriginX = SVGUtilities.convertSVGNumber(s) * scale;
307: } catch (NumberFormatException nfEx) {
308: throw new BridgeException(ctx, parentFontElement, nfEx,
309: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
310: SVG_HORIZ_ORIGIN_X_ATTRIBUTE, s });
311: }
312:
313: // horiz-origin-y
314: s = parentFontElement.getAttributeNS(null,
315: SVG_HORIZ_ORIGIN_Y_ATTRIBUTE);
316: if (s.length() == 0) {
317: // not specified so use the default value which is 0
318: s = SVG_HORIZ_ORIGIN_Y_DEFAULT_VALUE;
319: }
320: float horizOriginY;
321: try {
322: horizOriginY = SVGUtilities.convertSVGNumber(s) * -scale;
323: } catch (NumberFormatException nfEx) {
324: throw new BridgeException(ctx, glyphElement, nfEx,
325: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
326: SVG_HORIZ_ORIGIN_Y_ATTRIBUTE, s });
327: }
328:
329: Point2D horizOrigin = new Point2D.Float(horizOriginX,
330: horizOriginY);
331:
332: // return a new Glyph
333: return new Glyph(unicode, names, orientation, arabicForm, lang,
334: horizOrigin, vertOrigin, horizAdvX, vertAdvY,
335: glyphCode, tpi, dShape, glyphContentNode);
336: }
337: }
|