/***********************************************************************
* mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.components.visibleComponents.font.fontFactories;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.LinkedList;
import org.apache.batik.parser.PathParser;
import org.apache.batik.util.SVGConstants;
import org.mt4j.components.visibleComponents.font.IFont;
import org.mt4j.components.visibleComponents.font.IFontCharacter;
import org.mt4j.components.visibleComponents.font.VectorFont;
import org.mt4j.components.visibleComponents.font.VectorFontCharacter;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.MTColor;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import org.mt4j.util.xml.XmlHandler;
import org.mt4j.util.xml.svg.CustomPathHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import processing.core.PApplet;
/**
* Creates vector font from an svg font file.
* @author Christopher Ruff
*
*/
public class SvgFontFactory extends DefaultHandler implements IFontFactory {
// //Register the factory
// static{
// FontManager.getInstance().registerFontFactory(".ttf", new SvgFontFactory());
// }
private PApplet pa;
// private ArrayList<Vertex[]> pathVertexArrays;
// private HashMap<Vertex[], ArrayList<Vertex[]>> vertexArrToOutlinesList;
// private HashMap<BezierVertex, Vertex> cubicBezVertTOQuadricControlPoint;
private boolean verbose;
private String currentGlyphName;
private String currentUnicode;
private int currenthorizontalAdvX;
private String fontID;
private String fontFamily;
private int fontDefaultXAdvancing;
private ArrayList<VectorFontCharacter> characters;
private VectorFont svgFont;
private int fontMaxAscent;
private int fontMaxDescent;
private int font_units_per_em;
private int fontSize;
private CustomPathHandler pathHandler;
private PathParser pathParser;
private float scaleFactor;
private MTColor fillColor;
private MTColor strokeColor;
private boolean antiAliased;
public SvgFontFactory() { }
public IFont createFont(
PApplet pa,
String svgFontFileName,
int fontSize,
MTColor fillColor,
MTColor strokeColor
){
return this.createFont(pa, svgFontFileName, fontSize, fillColor, strokeColor, true);
}
/* (non-Javadoc)
* @see mTouch.components.visibleComponents.font.IFontFactory#loadFont(processing.core.PApplet, java.lang.String, int, float, float, float, float, float, float, float, float)
*/
public IFont createFont(
PApplet pa,
String svgFontFileName,
int fontSize,
MTColor fillColor,
MTColor strokeColor,
boolean antiAliased
){
this.pa = pa;
this.fontSize = fontSize;
this.fillColor = fillColor;
this.strokeColor = strokeColor;
this.antiAliased = antiAliased;
//List of all (prepared for stencil drawing) Glyph path vertices
// pathVertexArrays = new ArrayList<Vertex[]>();
// //Hashmap to get the list of partial- or sub-paths of a given glyph vertex array
// //the subpaths are needed to draw the outlines of the glyph
// vertexArrToOutlinesList = new HashMap<Vertex[], ArrayList<Vertex[]>>();
// //Because all quadric curves get converted to cubics,
// //but the original quadric controlpoint is needed for "T" tag
// cubicBezVertTOQuadricControlPoint = new HashMap<BezierVertex, Vertex>();
//List of all characters created during parsing
characters = new ArrayList<VectorFontCharacter>();
this.fontSize = fontSize;
//Defaults
currentGlyphName = "";
currentUnicode = "";
currenthorizontalAdvX = 500;
fontMaxAscent = 900;
fontMaxDescent = -200;
font_units_per_em = 1000;
fontID = "";
fontDefaultXAdvancing = 500;
scaleFactor = 0.2f;
svgFont = null;
verbose = false;
pathHandler = new CustomPathHandler();
pathParser = new PathParser();
pathParser.setPathHandler(pathHandler);
XmlHandler.getInstance().saxParse(
svgFontFileName,
this);
VectorFont font = this.getFont();
font.setFontFileName(svgFontFileName);
return font;
}
public String getFontFileSuffix() {
return ".svg";
}
@Override
public void startDocument() throws SAXException {
// pathVertexArrays.clear();
// vertexArrToOutlinesList.clear();
// cubicBezVertTOQuadricControlPoint.clear();
characters.clear();
if (verbose)
System.out.println("start Document ");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes){
//System.out.println("Start element: " + qName);
if(qName.equalsIgnoreCase("font") || qName.equalsIgnoreCase("font-face")){
for ( int i = 0; i < attributes.getLength(); i++ ){
String currentAttributeName = attributes.getQName(i);
String currentAttribueValue = attributes.getValue(i);
if (currentAttributeName.equalsIgnoreCase("id")){
fontID = currentAttribueValue;
}else if (currentAttributeName.equalsIgnoreCase("font-family")){
fontFamily = currentAttribueValue;
}else if (currentAttributeName.equalsIgnoreCase("horiz-adv-x")){
fontDefaultXAdvancing = Integer.parseInt(currentAttribueValue);
}else if (currentAttributeName.equalsIgnoreCase("ascent")){
fontMaxAscent = Integer.parseInt(currentAttribueValue);
}else if (currentAttributeName.equalsIgnoreCase("descent")){
fontMaxDescent = Integer.parseInt(currentAttribueValue);
}else if (currentAttributeName.equalsIgnoreCase("units-per-em")){
font_units_per_em = Integer.parseInt(currentAttribueValue);
//Get the desired fontsize scaling factor
int unitsPerEm = this.font_units_per_em;
int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
//System.out.println("Screen resolution: " + resolution);
this.scaleFactor = ((float)fontSize * (float)resolution) / (72F * (float)unitsPerEm);
//System.out.println("->Scalefactor: " + this.scaleFactor);
}
}
}
if(qName.equalsIgnoreCase(SVGConstants.SVG_GLYPH_TAG)|| qName.equalsIgnoreCase(SVGConstants.SVG_MISSING_GLYPH_TAG)){
VectorFontCharacter currentCharacter = null;
for ( int i = 0; i < attributes.getLength(); i++ ){
String currentAttributeName = attributes.getQName(i);
String currentAttribueValue = attributes.getValue(i);
if (currentAttributeName.equalsIgnoreCase("d")){
// ArrayList<SvgFontCharacter> characters = extractPath(currentAttribueValue);
//Parse the Paths's "d" attribute and create a new character
pathHandler = new CustomPathHandler();
pathParser.setPathHandler(pathHandler);
pathParser.parse(currentAttribueValue);
currentCharacter = this.createCharacter(pathHandler);
//Set unicode/name for missing glyph by hand
if (qName.equalsIgnoreCase("missing-glyph")){
currentUnicode = "missing-glyph";
currentGlyphName = "missing-glyph";
}
}else if (currentAttributeName.equalsIgnoreCase("unicode")){
currentUnicode = currentAttribueValue;
if (currentUnicode.equalsIgnoreCase(" ")){
Vertex[] spaceVerts = new Vertex[]{new Vertex(0,0,0), new Vertex(fontDefaultXAdvancing,0,0),new Vertex(fontDefaultXAdvancing,100,0), /*new Vertex(0,100,0)*/};
ArrayList<Vertex[]> spaceContours = new ArrayList<Vertex[]>();
spaceContours.add(spaceVerts);
VectorFontCharacter spaceCharacter = new VectorFontCharacter(spaceContours, pa);
// VectorFontCharacter spaceCharacter = new VectorFontCharacter(new Vertex[]{new Vertex(0,0,0), new Vertex(100,0,0),new Vertex(100,100,0),new Vertex(0,100,0)}, new ArrayList<Vertex[]>(), pa);
spaceCharacter.setPickable(false);
if (MT4jSettings.getInstance().isOpenGlMode()){
spaceCharacter.setUseDirectGL(true);
// spaceCharacter.generateAndUseDisplayLists();
}
spaceCharacter.setVisible(false);
spaceCharacter.setNoFill(true);
spaceCharacter.setNoStroke(true);
currentCharacter = spaceCharacter;
}
}else if (currentAttributeName.equalsIgnoreCase("glyph-name")){
currentGlyphName = currentAttribueValue;
if (currentUnicode.equalsIgnoreCase("space")){
Vertex[] spaceVerts = new Vertex[]{new Vertex(0,0,0), new Vertex(fontDefaultXAdvancing,0,0),new Vertex(fontDefaultXAdvancing,100,0), /*new Vertex(0,100,0)*/};
ArrayList<Vertex[]> spaceContours = new ArrayList<Vertex[]>();
spaceContours.add(spaceVerts);
VectorFontCharacter spaceCharacter = new VectorFontCharacter(spaceContours, pa);
// VectorFontCharacter spaceCharacter = new VectorFontCharacter(new Vertex[]{new Vertex(0,0,0), new Vertex(100,0,0),new Vertex(100,100,0),new Vertex(0,100,0)}, new ArrayList<Vertex[]>(), pa);
spaceCharacter.setPickable(false);
if (MT4jSettings.getInstance().isOpenGlMode()){
spaceCharacter.setUseDirectGL(true);
// spaceCharacter.generateAndUseDisplayLists();
}
spaceCharacter.setVisible(false);
spaceCharacter.setNoFill(true);
spaceCharacter.setNoStroke(true);
currentCharacter = spaceCharacter;
}
}else if (currentAttributeName.equalsIgnoreCase("horiz-adv-x")){
currenthorizontalAdvX = Integer.parseInt(currentAttribueValue);
}
}
if (currentCharacter != null){
currentCharacter.setName(currentGlyphName);
currentGlyphName = "";
currentCharacter.setUnicode(currentUnicode);
currentUnicode = "";
if (currenthorizontalAdvX != 0){
currentCharacter.setHorizontalDist(currenthorizontalAdvX);
}else{
currentCharacter.setHorizontalDist(fontDefaultXAdvancing); //Default if character, nor the font specified this
}
//TODO this caused performance problems in the past - reason UNKOWN! DISABLE IF PERFORMANCE DROPS SIGNIFICANTLY!
//Scale character advance distances according to fontsize
// float tmp = currentCharacter.getHorizontalDist() * (float)(1.0/(float)font_units_per_em);
// currentCharacter.setHorizontalDist(Math.round(tmp * fontSize));
currentCharacter.setHorizontalDist(Math.round(currentCharacter.getHorizontalDist() * this.scaleFactor));
//Set space's vertices to its horizontal advancement value
//the vertices are important for showing the textbox caret
//at the right position
if (currentCharacter.getUnicode().equals(" ") || currentUnicode.equalsIgnoreCase("space")){
int xadvance = currentCharacter.getHorizontalDist();
currentCharacter.setVertices(new Vertex[]{new Vertex(0,0,0), new Vertex(xadvance,0,0),new Vertex(xadvance,100,0) /*,new Vertex(0,100,0)*/});
}
characters.add(currentCharacter);
currenthorizontalAdvX = 0; //Reset
}
}
}
/**
*
* @param pathHandler
* @return
*/
private VectorFontCharacter createCharacter(CustomPathHandler pathHandler){
// Get the Vertices of the path
//Vertex[] originalPointsArray = pathHandler.getPathPointsArray();
//Get stencil prepared vertices (with reversed moveTo vertices added)
LinkedList<Vertex> pathPoints = pathHandler.getPathPoints();
/*
//Do this if you want stencil polygons
if (pathHandler.getReverseMoveToStack().size() <= 1){
//nicht adden
}else{
pathPoints.addAll(pathHandler.getReverseMoveToStack());
}
*/
Vertex[] allPathVertices = (Vertex[])pathPoints.toArray(new Vertex[pathPoints.size()]);
//Get Sub-Paths
ArrayList<Vertex[]> contours = pathHandler.getContours();
//Not really needed actually..
//add point array to list of all arrays
// pathVertexArrays.add(allPathVertices);
//Put the partial path list of this path into hashmap
// vertexArrToOutlinesList.put(allPathVertices, contours); //FIXME TEST
// CREATE THE Character \\
//Rotate because font characters 0,0 is at bottom left
Vertex.xRotateVectorArray(allPathVertices, new Vector3D((float)(currenthorizontalAdvX/2.0),0,0), 180);
//Scale character to desired fontsize
// Vertex.scaleVectorArray(allPathVertices, new Vector3D(0,0,0), (float)(1.0/(float)font_units_per_em));
// Vertex.scaleVectorArray(allPathVertices, new Vector3D(0,0,0), fontSize);
Vertex.scaleVectorArray(allPathVertices, new Vector3D(0,0,0), this.scaleFactor);
//TODO nur contours als parameter is intuitiver
VectorFontCharacter character = new VectorFontCharacter(contours, pa);
if (MT4jSettings.getInstance().isOpenGlMode())
character.setUseDirectGL(true);
//Color
character.setStrokeColor(new MTColor(strokeColor.getR(), strokeColor.getG(), strokeColor.getB(), strokeColor.getAlpha()));
character.setFillColor(new MTColor(fillColor.getR(), fillColor.getG(), fillColor.getB(), fillColor.getAlpha()));
character.setStrokeWeight(0.7f);
character.setPickable(false);
if (!antiAliased){
character.setNoStroke(true);
}else{
if (MT4jSettings.getInstance().isMultiSampling() && fillColor.equals(strokeColor)){
character.setNoStroke(true);
}else{
character.setNoStroke(false);
}
}
if (MT4jSettings.getInstance().isOpenGlMode())
character.generateAndUseDisplayLists();
return character;
}
@Override
public void endDocument() throws SAXException {
//Manually add a NEWLINE character to the font
Vertex[] nlVerts = new Vertex[]{new Vertex(0,0,0), new Vertex(200,0,0),new Vertex(200,100,0),/*new Vertex(0,100,0)*/};
ArrayList<Vertex[]> nlContours = new ArrayList<Vertex[]>();
nlContours.add(nlVerts);
VectorFontCharacter newLine = new VectorFontCharacter(nlContours, pa);
// VectorFontCharacter newLine = new VectorFontCharacter(new Vertex[]{new Vertex(0,0,0), new Vertex(200,0,0),new Vertex(200,100,0),new Vertex(0,100,0)}, new ArrayList<Vertex[]>(), pa);
newLine.setPickable(false);
newLine.setVisible(false);
newLine.setNoFill(true);
newLine.setNoStroke(true);
newLine.setHorizontalDist(0);
newLine.setUnicode("\n");
newLine.setName("newline");
characters.add(newLine);
//Manually add a TAB character to the font
int defaultTabWidth = 200;
Vertex[] tabVerts = new Vertex[]{new Vertex(0,0,0), new Vertex(200,0,0),new Vertex(200,100,0),/*new Vertex(0,100,0)*/};
ArrayList<Vertex[]> tabContours = new ArrayList<Vertex[]>();
tabContours.add(tabVerts);
VectorFontCharacter tab = new VectorFontCharacter(tabContours, pa);
// VectorFontCharacter tab = new VectorFontCharacter(new Vertex[]{new Vertex(0,0,0), new Vertex(defaultTabWidth,0,0),new Vertex(defaultTabWidth,100,0),new Vertex(0,100,0)}, new ArrayList<Vertex[]>(), pa);
tab.setPickable(false);
try {
IFontCharacter space = svgFont.getFontCharacterByUnicode(" ");
int tabWidth = 4*space.getHorizontalDist();
tab.setHorizontalDist(tabWidth);
tab.setVertices(new Vertex[]{new Vertex(0,0,0), new Vertex(tabWidth,0,0),new Vertex(tabWidth,100,0) /*,new Vertex(0,100,0)*/} );
} catch (Exception e) {
tab.setHorizontalDist(defaultTabWidth);
}
tab.setUnicode("\t");
tab.setName("tab");
tab.setVisible(false);
tab.setNoFill(true);
tab.setNoStroke(true);
characters.add(tab);
//Create a new SVG-FONT
// VectorFont svgFont = new VectorFont(this.getCharacters(), fontDefaultXAdvancing, fontFamily, fontMaxAscent, fontMaxDescent, font_units_per_em, fontSize,
VectorFont svgFont = new VectorFont((VectorFontCharacter[])characters.toArray(new VectorFontCharacter[characters.size()]), fontDefaultXAdvancing, fontFamily, fontMaxAscent, fontMaxDescent, font_units_per_em, fontSize,
fillColor,
strokeColor,
antiAliased
);
//TODO this caused performance problems - reason UNKOWN! DISABLE IF PERFORMANCE DROPS SIGNIFICANTLY!
//Set font max descent and ascent according to font size
// float tmp = fontMaxAscent * (float)(1.0/(float)font_units_per_em);
// fontMaxAscent = Math.round(tmp * fontSize);
// float tmp2 = fontMaxDescent * (float)(1.0/(float)font_units_per_em);
// fontMaxDescent = Math.round(tmp2 * fontSize);
fontMaxAscent = Math.round(fontMaxAscent * this.scaleFactor);
fontMaxDescent = Math.round(fontMaxDescent * this.scaleFactor);
svgFont.setFontMaxAscent(fontMaxAscent);
svgFont.setFontMaxDescent(fontMaxDescent);
svgFont.setFontId(fontID);
this.svgFont = svgFont;
// pathVertexArrays.clear();
// vertexArrToOutlinesList.clear();
// cubicBezVertTOQuadricControlPoint.clear();
characters.clear();
}
// /**
// * Returns an array containing all created svg characters
// * @return the created characters of the parsed svg font
// */
// private VectorFontCharacter[] getCharacters() {
// return (VectorFontCharacter[])characters.toArray(new VectorFontCharacter[characters.size()]);
// }
/**
* Returns the created SvgFont Object.
*
* @return the font
*/
public VectorFont getFont() {
return svgFont;
}
/**
* Enables debug messages.
* @param verbose
*/
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
@Override
public void skippedEntity(String arg0) throws SAXException {
super.skippedEntity(arg0);
if (verbose)
System.out.println("Skipped entity " + arg0);
}
@Override
public void error(SAXParseException arg0) throws SAXException {
super.error(arg0);
System.err.println(arg0.getMessage());
}
@Override
public void fatalError(SAXParseException arg0) throws SAXException {
super.fatalError(arg0);
System.err.println(arg0.getMessage());
}
@Override
public void warning(SAXParseException arg0) throws SAXException {
super.warning(arg0);
System.err.println(arg0.getMessage());
}
}