/*
  Candy 2 - SVG Importer for Processing - http://processing.org
  Copyright (c) 2006 Michael Chang (Flux)
  http://www.ghost-hack.com/
  Revised and expanded by Ben Fry for inclusion as a core library
  Copyright (c) 2006-08 Ben Fry and Casey Reas
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License version 2.1 as published by the Free Software Foundation.
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*/
package processing.candy;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Hashtable;
import processing.core.*;
import processing.xml.*;
/**
 * Candy is a minimal SVG import library for Processing.
 * Candy was written by Michael Chang, and later revised and
 * expanded for use as a Processing core library by Ben Fry.
 * 
 * SVG stands for Scalable Vector Graphics, a portable graphics
 * format. It is a vector format so it allows for infinite resolution
 * and relatively minute file sizes. Most modern media software
 * can view SVG files, including Firefox, Adobe products, etc.
 * You can use something like Illustrator to edit SVG files.
 * 
 * We have no intention of turning this into a full-featured SVG library.
 * The goal of this project is a basic shape importer that is small enough
 * to be included with applets, meaning that its download size should be
 * in the neighborhood of 25-30k. Because of this size, it is not made part
 * of processing.core, because it would increase the download size of any
 * applet by 20%, and it's not a feature that will be used by the majority
 * of our audience. For more sophisticated import/export, consider the
 * Batik  library
 * from the Apache Software Foundation. Future improvements to this
 * library may focus on this properly supporting a specific subset of
 * SVG, for instance the simpler SVG profiles known as
 * SVG Tiny or Basic ,
 * although we still would not support the interactivity options.
 * 
 * This library was specifically tested under SVG files created with Adobe
 * Illustrator. We can't guarantee that it will work for any SVGs created with
 * other software. In the future we would like to improve compatibility with
 * Open Source software such as InkScape, however initial tests show its
 * base implementation produces more complicated files, and this will require
 * more time.
 * 
 * An SVG created under Illustrator must be created in one of two ways:
 * 
 * File → Save for Web (or control-alt-shift-s on a PC). Under
 * settings, make sure the CSS properties is set to "Presentation Attributes".
 *  With Illustrator CS2, it is also possible to use "Save As" with "SVG"
 * as the file setting, but the CSS properties should also be set similarly.
 *   
 * Saving it any other way will most likely break Candy.
 *
 *  
 *
 * A minimal example program using Candy:
 * (assuming a working moo.svg is in your data folder)
 *
 * 
 * import processing.candy.*;
 * import processing.xml.*;
 *
 * SVG moo;
 * void setup() {
 *   size(400,400);
 *   moo = new SVG("moo.svg",this);
 * }
 * void draw() {
 *   moo.draw();
 * }
 *  
 *
 * Note that processing.xml needs to be imported as well. 
 * This may not be required when running code within the Processing
 * environment, but when exported it may cause a NoClassDefError.
 * This will be fixed in later releases of Processing
 * (Bug 518 ).
 *
 *  
 *
 * February 2008 revisions by fry (Processing 0136)
 * 
 *  Added support for quadratic curves in paths (Q, q, T, and t operators)
 *   Support for reading SVG font data (though not rendering it yet) 
 *   
 *  
 * Revisions for "Candy 2" November 2006 by fry
 * 
 *  Switch to the new processing.xml library
 *   Several bug fixes for parsing of shape data
 *   Support for linear and radial gradients
 *   Support for additional types of shapes
 *   Added compound shapes (shapes with interior points)
 *   Added methods to get shapes from an internal table
 *   
 *
 * Revision 10/31/06 by flux
 * 
 *  Now properly supports Processing 0118
 *   Fixed a bunch of things for Casey's students and general buggity.
 *   Will now properly draw #FFFFFFFF colors (were being represented as -1)
 *   SVGs without  tags are now properly caught and loaded
 *  Added a method customStyle() for overriding SVG colors/styles
 *   Added a method SVGStyle() to go back to using SVG colors/styles
 *     
 *
 * Some SVG objects and features may not yet be supported.
 * Here is a partial list of non-included features
 * 
 *  Rounded rectangles
 *   Drop shadow objects
 *   Typography
 *   Layers  added for Candy 2
 *   Patterns
 *   Embedded images
 *   
 *
 * For those interested, the SVG specification can be found
 * here .
 */
public class SVG {
    protected PApplet parent;
    public float width;
    public float height;
    protected Hashtable table = new Hashtable();
    protected XMLElement svg;
    protected BaseObject root;
    protected boolean ignoreStyles = false;
    int drawMode = PConstants.CORNER;
    /**
     * Initializes a new SVG Object with the given filename.
     */
    public SVG(PApplet parent, String filename) {
        // this will grab the root document, starting 
        // the xml version and initial comments are ignored
    	this(parent, new XMLElement(parent, filename));
    }
    
    
    /**
     * Initializes a new SVG Object with the given filename.
     */
    public SVG(PApplet parent, XMLElement svg) {
        this.parent = parent;
        this.svg = svg;
        if (!svg.getName().equals("svg")) {
            throw new RuntimeException("root is not , it's <" + svg.getName() + ">");
        }
        // not proper parsing of the viewBox, but will cover us for cases where
        // the width and height of the object is not specified
        String viewBoxStr = svg.getStringAttribute("viewBox");
        if (viewBoxStr != null) {
            int[] viewBox = PApplet.parseInt(PApplet.splitTokens(viewBoxStr));
            width = viewBox[2];
            height = viewBox[3];
        }
        // TODO if viewbox is not same as width/height, then use it to scale
        // the original objects. for now, viewbox only used when width/height
        // are empty values (which by the spec means w/h of "100%"
        String unitWidth = svg.getStringAttribute("width");
        String unitHeight = svg.getStringAttribute("height");
        if (unitWidth != null) {
            width = parseUnitSize(unitWidth);
            height = parseUnitSize(unitHeight);
        } else {
            if ((width == 0) || (height == 0)) {
                //throw new RuntimeException("width/height not specified");
            	System.err.println("The width and/or height is not " +
            			           "readable in the  tag of this file.");
            	// For the spec, the default is 100% and 100%. For purposes 
            	// here, insert a dummy value because this is prolly just a 
            	// font or something for which the w/h doesn't matter.
            	width = 1;
            	height = 1;
            }
        }
        /*
        PApplet.println("document has " + document.getChildCount() + " children");
        //Get the xml child node we need
        XMLElement doc = document.getChild(1);
        PApplet.println(doc);
        if (true) return;
        */
        /*
        //XMLElement entSVG = doc.getChild(0);
        //XMLElement svg = entSVG.getChild(1);
        //While we're doing that, save the width and height too
        //svgWidth = svg.getIntAttribute("width");
        //svgHeight = svg.getIntAttribute("height");
        //Catch exception when SVG doesn't have a  tag
        XMLElement graphics;
        String nameOfFirstChild = svg.getChild(1).toString();
        if(nameOfFirstChild.equals(""))
            graphics = svg.getChild(1);
        else
            graphics = svg;
        this.svgData = svg;
        */
        //parseChildren(document);
        root = new Group(null, svg);
        /*
        XMLElement graphics = null;
        //Print SVG on construction
        //Use this for debugging
        //svg.printElementTree(" .");
        */
    }
    /**
     * Internal method used to clone an object and return the subtree.
     */
    protected SVG(PApplet parent, float width, float height, Hashtable table,
                  BaseObject obj, boolean styleOverride) {
        this.parent = parent;
        this.width = width;
        this.height = height;
        this.table = table;
        this.root = obj;
        this.svg = obj.element;
        this.ignoreStyles = styleOverride;
    }
    /**
     * Parse a size that may have a suffix for its units.
     * Ignoring cases where this could also be a percentage.
     * The units  spec:
     * 
     * "1pt" equals "1.25px" (and therefore 1.25 user units)
     *  "1pc" equals "15px" (and therefore 15 user units)
     *  "1mm" would be "3.543307px" (3.543307 user units)
     *  "1cm" equals "35.43307px" (and therefore 35.43307 user units)
     *  "1in" equals "90px" (and therefore 90 user units)
     *   
     */
    public float parseUnitSize(String text) {
        int len = text.length() - 2;
        if (text.endsWith("pt")) {
            return PApplet.parseFloat(text.substring(0, len)) * 1.25f;
        } else if (text.endsWith("pc")) {
            return PApplet.parseFloat(text.substring(0, len)) * 15;
        } else if (text.endsWith("mm")) {
            return PApplet.parseFloat(text.substring(0, len)) * 3.543307f;
        } else if (text.endsWith("cm")) {
            return PApplet.parseFloat(text.substring(0, len)) * 35.43307f;
        } else if (text.endsWith("in")) {
            return PApplet.parseFloat(text.substring(0, len)) * 90;
        } else if (text.endsWith("px")) {
            return PApplet.parseFloat(text.substring(0, len));
        } else {
            return PApplet.parseFloat(text);
        }
    }
    /**
     * Get a particular element based on its SVG ID. When editing SVG by hand,
     * this is the id="" tag on any SVG element. When editing from Illustrator,
     * these IDs can be edited by expanding the layers palette. The names used
     * in the layers palette, both for the layers or the shapes and groups
     * beneath them can be used here.
     * 
     * // This code grabs "Layer 3" and the shapes beneath it.
     * SVG layer3 = svg.get("Layer 3");
     *  
     */
    public SVG get(String name) {
        BaseObject obj = (BaseObject) table.get(name);
        if (obj == null) {
            // try with underscores instead of spaces
            obj = (BaseObject) table.get(name.replace(' ', '_'));
        }
        if (obj != null) {
            return new SVG(parent, width, height, table, obj, ignoreStyles);
        }
        return null;
    }
    // grab the (fill) gradient from a particular object by name
    // and apply it to either the stroke or fill
    // based on
    protected Paint getGradient(String name, float cx, float cy, float r) {
        BaseObject obj = (BaseObject) table.get(name);
        if (obj == null) {
            // try with underscores instead of spaces
            obj = (BaseObject) table.get(name.replace(' ', '_'));
        }
        if (obj != null) {
            if (obj.fillGradient != null) {
                return obj.calcGradientPaint(obj.fillGradient, cx, cy, r);
            }
        }
        throw new RuntimeException("No gradient found for shape " + name);
    }
    protected Paint getGradient(String name, float x1, float y1, float x2, float y2) {
        BaseObject obj = (BaseObject) table.get(name);
        if (obj == null) {
            // try with underscores instead of spaces
            obj = (BaseObject) table.get(name.replace(' ', '_'));
        }
        if (obj != null) {
            if (obj.fillGradient != null) {
                return obj.calcGradientPaint(obj.fillGradient, x1, y1, x2, y2);
            }
        }
        throw new RuntimeException("No gradient found for shape " + name);
    }
    public void strokeGradient(String name, float x, float y, float r) {
        Paint paint = getGradient(name, x, y, r);
        if (parent.g instanceof PGraphicsJava2D) {
            PGraphicsJava2D p2d = ((PGraphicsJava2D) parent.g);
            p2d.strokeGradient = true;
            p2d.strokeGradientObject = paint;
        }
    }
    public void strokeGradient(String name, float x1, float y1, float x2, float y2) {
        Paint paint = getGradient(name, x1, y1, x2, y2);
        if (parent.g instanceof PGraphicsJava2D) {
            PGraphicsJava2D p2d = ((PGraphicsJava2D) parent.g);
            p2d.strokeGradient = true;
            p2d.strokeGradientObject = paint;
        }
    }
    public void fillGradient(String name, float x, float y, float r) {
        Paint paint = getGradient(name, x, y, r);
        if (parent.g instanceof PGraphicsJava2D) {
            PGraphicsJava2D p2d = ((PGraphicsJava2D) parent.g);
            p2d.fillGradient = true;
            p2d.fillGradientObject = paint;
        }
    }
    public void fillGradient(String name, float x1, float y1, float x2, float y2) {
        Paint paint = getGradient(name, x1, y1, x2, y2);
        if (parent.g instanceof PGraphicsJava2D) {
            PGraphicsJava2D p2d = ((PGraphicsJava2D) parent.g);
            p2d.fillGradient = true;
            p2d.fillGradientObject = paint;
        }
    }
    /**
     * Temporary hack for gradient handling. This is not supported
     * and will be removed from future releases.
     */
    /*
    public void drawStyles() {
        root.drawStyles();
        //PApplet.println(root);
        if (root instanceof VectorObject) {
            ((VectorObject)root).drawStyles();
        } else {
            PApplet.println("Only use drawStyles() on an object, not a group.");
        }
    }
    */
    public void draw() {
        if (drawMode == PConstants.CENTER) {
            parent.pushMatrix();
            parent.translate(-width/2, -height/2);
            drawImpl();
            parent.popMatrix();
        } else if ((drawMode == PConstants.CORNER) ||
                   (drawMode == PConstants.CORNERS)) {
            drawImpl();
        }
    }
    /**
     * Convenience method to draw at a particular location.
     */
    public void draw(float x, float y) {
        parent.pushMatrix();
        if (drawMode == PConstants.CENTER) {
            parent.translate(x - width/2, y - height/2);
        } else if ((drawMode == PConstants.CORNER) ||
                   (drawMode == PConstants.CORNERS)) {
            parent.translate(x, y);
        }
        drawImpl();
        parent.popMatrix();
    }
    public void draw(float x, float y, float c, float d) {
        parent.pushMatrix();
        if (drawMode == PConstants.CENTER) {
            // x and y are center, c and d refer to a diameter
            parent.translate(x - c/2f, y - d/2f);
            parent.scale(c / width, d / height);
        } else if (drawMode == PConstants.CORNER) {
            parent.translate(x, y);
            parent.scale(c / width, d / height);
        } else if (drawMode == PConstants.CORNERS) {
            // c and d are x2/y2, make them into width/height
            c -= x;
            d -= y;
            // then same as above
            parent.translate(x, y);
            parent.scale(c / width, d / height);
        }
        drawImpl();
        parent.popMatrix();
    }
    /**
     * Draws the SVG document.
     */
    public void drawImpl() {
        boolean stroke = parent.g.stroke;
        int strokeColor = parent.g.strokeColor;
        float strokeWeight = parent.g.strokeWeight;
        int strokeCap = parent.g.strokeCap;
        int strokeJoin= parent.g.strokeJoin;
        boolean fill = parent.g.fill;
        int fillColor = parent.g.fillColor;
        int ellipseMode = parent.g.ellipseMode;
        root.draw();
        parent.g.stroke = stroke;
        parent.g.strokeColor = strokeColor;
        parent.g.strokeWeight = strokeWeight;
        parent.g.strokeCap = strokeCap;
        parent.g.strokeJoin = strokeJoin;
        parent.g.fill = fill;
        parent.g.fillColor = fillColor;
        parent.g.ellipseMode = ellipseMode;
    }
    /**
     * Set the orientation for drawn objects, similar to PImage.imageMode().
     * @param which Either CORNER, CORNERS, or CENTER.
     */
    public void drawMode(int which) {
        drawMode = which;
    }
    /**
     * Overrides SVG-set styles and uses PGraphics styles and colors.
     * Identical to ignoreStyles(true).
     */
    public void ignoreStyles() {
        ignoreStyles(true);
    }
    /**
     * Enables or disables style information (fill and stroke) set in the file.
     * @param state true to use user-specified stroke/fill, false for svg version
     */
    public void ignoreStyles(boolean state) {
        ignoreStyles = state;
    }
    /**
     * Prints out the SVG document useful for parsing
     */
    public void print() {
        PApplet.println(svg.toString());
    }
    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    protected abstract class BaseObject {
        String id;
        XMLElement element;
        // set to false if the object is hidden in the layers palette
        boolean display;
        boolean stroke;
        int strokeColor;
        float strokeWeight; // default is 1
        int strokeCap;
        int strokeJoin;
        Gradient strokeGradient;
        Paint strokeGradientPaint;
        String strokeName;  // id of another object, gradients only?
        boolean fill;
        int fillColor;
        Gradient fillGradient;
        Paint fillGradientPaint;
        String fillName;  // id of another object
        boolean hasTransform;
        PMatrix transformation;
        float strokeOpacity;
        float fillOpacity;
        float opacity;
        public BaseObject(BaseObject parent, XMLElement properties) {
            if (parent == null) {
                // set values to their defaults according to the SVG spec
                stroke = false;
                strokeColor = 0xff000000;
                strokeWeight = 1;
                strokeCap = PConstants.SQUARE;  // equivalent to BUTT in svg spec
                strokeJoin = PConstants.MITER;
                strokeGradient = null;
                strokeGradientPaint = null;
                strokeName = null;
                fill = true;
                fillColor = 0xff000000;
                fillGradient = null;
                fillGradientPaint = null;
                fillName = null;
                
                //hasTransform = false;
                //transformation = null; //new float[] { 1, 0, 0, 1, 0, 0 };
                strokeOpacity = 255;
                fillOpacity = 255;
                opacity = 1;
            } else {
                stroke = parent.stroke;
                strokeColor = parent.strokeColor;
                strokeWeight = parent.strokeWeight;
                strokeCap = parent.strokeCap;
                strokeJoin = parent.strokeJoin;
                strokeGradient = parent.strokeGradient;
                strokeGradientPaint = parent.strokeGradientPaint;
                strokeName = parent.strokeName;
                fill = parent.fill;
                fillColor = parent.fillColor;
                fillGradient = parent.fillGradient;
                fillGradientPaint = parent.fillGradientPaint;
                fillName = parent.fillName;
                //hasTransform = parent.hasTransform;
                //transformation = parent.transformation;
                strokeOpacity = parent.strokeOpacity;
                fillOpacity = parent.fillOpacity;
                opacity = parent.opacity;
            }
            element = properties;
            id = properties.getStringAttribute("id");
            if (id != null) {
                table.put(id, this);
                //System.out.println("now parsing " + id);
            }
            
            String displayStr = properties.getStringAttribute("display", "inline");
            display = !displayStr.equals("none");
            
            getColors(properties);
            getTransformation(properties);
        }
        private void getTransformation(XMLElement properties) {
          if (properties.hasAttribute("transform"))  {
            String transform = properties.getStringAttribute("transform");
            setTransformation(transform);
          }
        }
        protected void getColors(XMLElement properties) {
          
          if (properties.hasAttribute("opacity")) {
            String opacityText = properties.getStringAttribute("opacity");
            setOpacity(opacityText);
          }
          
          if (properties.hasAttribute("stroke")) {
            String strokeText = properties.getStringAttribute("stroke");
            setStroke(strokeText);
          }
          
          if (properties.hasAttribute("stroke-width")) {
            // if NaN (i.e. if it's 'inherit') then default back to the inherit setting
            String lineweight = properties.getStringAttribute("stroke-width");
            setStrokeWeight(lineweight);
          }
          if (properties.hasAttribute("stroke-linejoin")) {
            String linejoin = properties.getStringAttribute("stroke-linejoin");
            setStrokeJoin(linejoin);
          }
          
          if (properties.hasAttribute("stroke-linecap")) {
            String linecap = properties.getStringAttribute("stroke-linecap");
            setStrokeCap(linecap);
          }
          
          
          // fill defaults to black (though stroke defaults to "none")
          // http://www.w3.org/TR/SVG/painting.html#FillProperties
          if (properties.hasAttribute("fill")) {
            String fillText = properties.getStringAttribute("fill");
            setFill(fillText);
            
          }
          
          if (properties.hasAttribute("style")) {
            String styleText = properties.getStringAttribute("style");
            String[] styleTokens = PApplet.splitTokens(styleText, ";");
            
            //PApplet.println(styleTokens);
            for(int i = 0; i < styleTokens.length; i++){
              String[] tokens = PApplet.splitTokens(styleTokens[i], ":");
              //PApplet.println(tokens);
              tokens[0] = PApplet.trim(tokens[0]);
              
              if(tokens[0].equals("fill")){
                setFill(tokens[1]);
                
              }else if(tokens[0].equals("fill-opacity")){
                setFillOpacity(tokens[1]);        
                
              }else if(tokens[0].equals("stroke")){
                setStroke(tokens[1]);
                
              }else if(tokens[0].equals("stroke-width")){
                setStrokeWeight(tokens[1]);
                
              }else if(tokens[0].equals("stroke-linecap")){
                setStrokeCap(tokens[1]);
                
              }else if(tokens[0].equals("stroke-linejoin")){
                setStrokeJoin(tokens[1]); 
                
              }else if(tokens[0].equals("stroke-opacity")){
                setStrokeOpacity(tokens[1]);
                
              }else if(tokens[0].equals("opacity")){
                setOpacity(tokens[1]);
                
              }else{
                // Other attributes are not yet implemented
              }
            }
          }
        }
      void setTransformation(String transfText){
        String[] transfTokens = PApplet.splitTokens(transfText, ")");
        //if(transformation == null){
        transformation = new PMatrix();
        //}
        hasTransform = true;
        
        // Loop through all transformations
        for(int i=0; i tag.");
                }
            }
        }
        public void drawShape() {
          if (display) {
            for (int i = 0; i < objectCount; i++) {
              objects[i].draw();
            }
          }
        }
    }
    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    abstract private class Gradient extends BaseObject {
        AffineTransform transform;
        float[] offset;
        int[] color;
        int count;
        public Gradient(BaseObject parent, XMLElement properties) {
            super(parent, properties);
            XMLElement elements[] = properties.getChildren();
            offset = new float[elements.length];
            color = new int[elements.length];
            // ");
                }
            }
    	}
		protected void drawShape() {
			// does nothing for fonts
		}
		
		
		public void drawString(String str, float x, float y, float size) {
			// 1) scale by the 1.0/unitsPerEm
			// 2) scale up by a font size
			parent.pushMatrix();
			float s =  size / (float) face.unitsPerEm;
			//System.out.println("scale is " + s);
			// swap y coord at the same time, since fonts have y=0 at baseline
			parent.translate(x, y);
			parent.scale(s, -s);
			char[] c = str.toCharArray();
			for (int i = 0; i < c.length; i++) {
				// call draw on each char (pulling it w/ the unicode table)
				FontGlyph fg = (FontGlyph) unicodeGlyphs.get(new Character(c[i]));
				if (fg != null) {
					fg.draw();
					// add horizAdvX/unitsPerEm to the x coordinate along the way
					parent.translate(fg.horizAdvX, 0);
				} else {
					System.err.println("'" + c[i] + "' not available.");
				}
			}
			parent.popMatrix();
		}
		
		
		public void drawChar(char c, float x, float y, float size) {
			parent.pushMatrix();
			float s =  size / (float) face.unitsPerEm;
			parent.translate(x, y);
			parent.scale(s, -s);
			FontGlyph fg = (FontGlyph) unicodeGlyphs.get(new Character(c));
			if (fg != null) fg.draw();
			parent.popMatrix();
		}
    }
    
    
    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    
    public class FontFace extends BaseObject {
    	int horizOriginX;  // dflt 0
    	int horizOriginY;  // dflt 0
    	int horizAdvX;     // no dflt?
    	int vertOriginX;   // dflt horizAdvX/2
    	int vertOriginY;   // dflt ascent
    	int vertAdvY;      // dflt 1em (unitsPerEm value)
    	String fontFamily;
    	int fontWeight;    // can also be normal or bold (also comma separated)
    	String fontStretch;
    	int unitsPerEm;    // dflt 1000
    	int[] panose1;     // dflt "0 0 0 0 0 0 0 0 0 0"
    	int ascent;
    	int descent;
    	int[] bbox;        // spec says comma separated, tho not w/ forge
    	int underlineThickness;
    	int underlinePosition;
    	//String unicodeRange; // gonna ignore for now
    	
    	public FontFace(BaseObject parent, XMLElement properties) {
    		super(parent, properties);
    		
    		unitsPerEm = properties.getIntAttribute("units-per-em", 1000);
    	}
    	
    	
		protected void drawShape() {
			// nothing to draw in the font face attribute
		}
    }
    
    
    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    public class FontGlyph extends Path {
    	String name;
    	//String unicode;
    	char unicode; //c;
    	int horizAdvX;
    	
        public FontGlyph(BaseObject parent, XMLElement properties) {
            super(parent, properties);
            
            name = properties.getStringAttribute("glyph-name");
            String u = properties.getStringAttribute("unicode");
            unicode = 0;
            if (u != null) {
            	if (u.length() == 1) {
            		unicode = u.charAt(0);
            		//System.out.println("unicode for " + name + " is " + u);
            	} else {
            		System.err.println("unicode for " + name + 
            					       " is more than one char: " + u);
            	}
            }
            horizAdvX = properties.getIntAttribute("horiz-adv-x", 0);
        }
    }
    */
}