/* DefaultTeXFontParser.java
* =========================================================================
* This file is originally part of the JMathTeX Library - http://jmathtex.sourceforge.net
*
* Copyright (C) 2004-2007 Universiteit Gent
* Copyright (C) 2009 DENIZET Calixte
*
* 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 2 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.
*
* A copy of the GNU General Public License can be found in the file
* LICENSE.txt provided with the source distribution of this program (see
* the META-INF directory in the source jar). This license can also be
* found on the GNU website at http://www.gnu.org/licenses/gpl.html.
*
* If you did not receive a copy of the GNU General Public License along
* with this program, contact the lead developer, or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* Linking this library statically or dynamically with other modules
* is making a combined work based on this library. Thus, the terms
* and conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce
* an executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under terms
* of your choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module.
* An independent module is a module which is not derived from or based
* on this library. If you modify this library, you may extend this exception
* to your version of the library, but you are not obliged to do so.
* If you do not wish to do so, delete this exception statement from your
* version.
*
*/
/* Modified by Calixte Denizet */
package org.scilab.forge.jlatexmath;
import java.lang.reflect.Method;
import java.awt.Font;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.awt.GraphicsEnvironment;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
/**
* Parses the font information from an XML-file.
*/
public class DefaultTeXFontParser {
/**
* if the register font cannot be found, we display an error message
* but we do it only once
*/
private static boolean registerFontExceptionDisplayed = false;
private static boolean shouldRegisterFonts = true;
private static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
private static interface CharChildParser { // NOPMD
public void parse(Element el, char ch, FontInfo info) throws XMLResourceParseException;
}
private static class ExtensionParser implements CharChildParser {
ExtensionParser() {
// avoid generation of access class
}
public void parse(Element el, char ch, FontInfo info) throws ResourceParseException {
int[] extensionChars = new int[4];
// get required integer attributes
extensionChars[DefaultTeXFont.REP] = DefaultTeXFontParser
.getIntAndCheck("rep", el);
// get optional integer attributes
extensionChars[DefaultTeXFont.TOP] = DefaultTeXFontParser
.getOptionalInt("top", el, DefaultTeXFont.NONE);
extensionChars[DefaultTeXFont.MID] = DefaultTeXFontParser
.getOptionalInt("mid", el, DefaultTeXFont.NONE);
extensionChars[DefaultTeXFont.BOT] = DefaultTeXFontParser
.getOptionalInt("bot", el, DefaultTeXFont.NONE);
// parsing OK, add extension info
info.setExtension(ch, extensionChars);
}
}
private static class KernParser implements CharChildParser {
KernParser() {
// avoid generation of access class
}
public void parse(Element el, char ch, FontInfo info) throws ResourceParseException {
// get required integer attribute
int code = DefaultTeXFontParser.getIntAndCheck("code", el);
// get required float attribute
float kernAmount = DefaultTeXFontParser.getFloatAndCheck("val", el);
// parsing OK, add kern info
info.addKern(ch, (char) code, kernAmount);
}
}
private static class LigParser implements CharChildParser {
LigParser() {
// avoid generation of access class
}
public void parse(Element el, char ch, FontInfo info) throws ResourceParseException {
// get required integer attributes
int code = DefaultTeXFontParser.getIntAndCheck("code", el);
int ligCode = DefaultTeXFontParser.getIntAndCheck("ligCode", el);
// parsing OK, add ligature info
info.addLigature(ch, (char) code, (char) ligCode);
}
}
private static class NextLargerParser implements CharChildParser {
NextLargerParser() {
// avoid generation of access class
}
public void parse(Element el, char ch, FontInfo info) throws ResourceParseException {
// get required integer attributes
String fontId = DefaultTeXFontParser.getAttrValueAndCheckIfNotNull("fontId", el);
int code = DefaultTeXFontParser.getIntAndCheck("code", el);
// parsing OK, add "next larger" info
info.setNextLarger(ch, (char) code, Font_ID.indexOf(fontId));
}
}
public static final String RESOURCE_NAME = "DefaultTeXFont.xml";
public static final String STYLE_MAPPING_EL = "TextStyleMapping";
public static final String SYMBOL_MAPPING_EL = "SymbolMapping";
public static final String GEN_SET_EL = "GeneralSettings";
public static final String MUFONTID_ATTR = "mufontid";
public static final String SPACEFONTID_ATTR = "spacefontid";
protected static ArrayList<String> Font_ID = new ArrayList<String>();
private static Map<String,Integer> rangeTypeMappings = new HashMap<String,Integer>();
private static Map<String,CharChildParser> charChildParsers = new HashMap<String,CharChildParser>();
private Map<String,CharFont[]> parsedTextStyles;
private Element root;
private Object base = null;
static {
// string-to-constant mappings
setRangeTypeMappings();
// parsers for the child elements of a "Char"-element
setCharChildParsers();
}
public DefaultTeXFontParser() throws ResourceParseException {
this(DefaultTeXFontParser.class.getResourceAsStream(RESOURCE_NAME), RESOURCE_NAME);
}
public DefaultTeXFontParser(InputStream file, String name) throws ResourceParseException {
factory.setIgnoringElementContentWhitespace(true);
factory.setIgnoringComments(true);
try {
root = factory.newDocumentBuilder().parse(file).getDocumentElement();
} catch (Exception e) { // JDOMException or IOException
throw new XMLResourceParseException(name, e);
}
}
public DefaultTeXFontParser(Object base , InputStream file, String name) throws ResourceParseException {
this.base = base;
factory.setIgnoringElementContentWhitespace(true);
factory.setIgnoringComments(true);
try {
root = factory.newDocumentBuilder().parse(file).getDocumentElement();
} catch (Exception e) { // JDOMException or IOException
throw new XMLResourceParseException(name, e);
}
}
private static void setCharChildParsers() {
charChildParsers.put("Kern", new KernParser());
charChildParsers.put("Lig", new LigParser());
charChildParsers.put("NextLarger", new NextLargerParser());
charChildParsers.put("Extension", new ExtensionParser());
}
public FontInfo[] parseFontDescriptions(FontInfo[] fi, InputStream file, String name) throws ResourceParseException {
if (file == null) {
return fi;
}
ArrayList<FontInfo> res = new ArrayList<FontInfo>(Arrays.asList(fi));
Element font;
try {
font = factory.newDocumentBuilder().parse(file).getDocumentElement();
} catch (Exception e) {
throw new XMLResourceParseException("Cannot find the file " + name + "!" + e.toString());
}
String fontName = getAttrValueAndCheckIfNotNull("name", font);
// get required integer attribute
String fontId = getAttrValueAndCheckIfNotNull("id", font);
if (Font_ID.indexOf(fontId) < 0)
Font_ID.add(fontId);
else throw new FontAlreadyLoadedException("Font " + fontId + " is already loaded !");
// get required real attributes
float space = getFloatAndCheck("space", font);
float xHeight = getFloatAndCheck("xHeight", font);
float quad = getFloatAndCheck("quad", font);
// get optional integer attribute
int skewChar = getOptionalInt("skewChar", font, -1);
// get optional boolean for unicode
int unicode = getOptionalInt("unicode", font, 0);
// get different versions of a font
String bold = null;
try {
bold = getAttrValueAndCheckIfNotNull("boldVersion", font);
} catch (ResourceParseException e) {}
String roman = null;
try {
roman = getAttrValueAndCheckIfNotNull("romanVersion", font);
} catch (ResourceParseException e) {}
String ss = null;
try {
ss = getAttrValueAndCheckIfNotNull("ssVersion", font);
} catch (ResourceParseException e) {}
String tt = null;
try {
tt = getAttrValueAndCheckIfNotNull("ttVersion", font);
} catch (ResourceParseException e) {}
String it = null;
try {
it = getAttrValueAndCheckIfNotNull("itVersion", font);
} catch (ResourceParseException e) {}
String path = name.substring(0, name.lastIndexOf("/") + 1) + fontName;
// create FontInfo-object
FontInfo info = new FontInfo(Font_ID.indexOf(fontId), base, path, fontName, unicode, xHeight, space, quad, bold, roman, ss, tt, it);
if (skewChar != -1) // attribute set
info.setSkewChar((char) skewChar);
// process all "Char"-elements
NodeList listF = font.getElementsByTagName("Char");
for (int j = 0; j < listF.getLength(); j++)
processCharElement((Element) listF.item(j), info);
// parsing OK, add to table
res.add(info);
for (int i = 0; i < res.size(); i++) {
FontInfo fin = res.get(i);
fin.setBoldId(Font_ID.indexOf(fin.boldVersion));
fin.setRomanId(Font_ID.indexOf(fin.romanVersion));
fin.setSsId(Font_ID.indexOf(fin.ssVersion));
fin.setTtId(Font_ID.indexOf(fin.ttVersion));
fin.setItId(Font_ID.indexOf(fin.itVersion));
}
parsedTextStyles = parseStyleMappings();
return res.toArray(fi);
}
public FontInfo[] parseFontDescriptions(FontInfo[] fi) throws ResourceParseException {
Element fontDescriptions = (Element)root.getElementsByTagName("FontDescriptions").item(0);
if (fontDescriptions != null) { // element present
NodeList list = fontDescriptions.getElementsByTagName("Metrics");
for (int i = 0; i < list.getLength(); i++) {
// get required string attribute
String include = getAttrValueAndCheckIfNotNull("include", (Element)list.item(i));
if (base == null) {
fi = parseFontDescriptions(fi, DefaultTeXFontParser.class.getResourceAsStream(include), include);
} else {
fi = parseFontDescriptions(fi, base.getClass().getResourceAsStream(include), include);
}
}
}
return fi;
}
protected void parseExtraPath() throws ResourceParseException {
Element syms = (Element)root.getElementsByTagName("TeXSymbols").item(0);
if (syms != null) { // element present
// get required string attribute
String include = getAttrValueAndCheckIfNotNull("include", syms);
SymbolAtom.addSymbolAtom(base.getClass().getResourceAsStream(include), include);
}
Element settings = (Element)root.getElementsByTagName("FormulaSettings").item(0);
if (settings != null) { // element present
// get required string attribute
String include = getAttrValueAndCheckIfNotNull("include", settings);
TeXFormula.addSymbolMappings(base.getClass().getResourceAsStream(include), include);
}
}
private static void processCharElement(Element charElement, FontInfo info)
throws ResourceParseException {
// retrieve required integer attribute
char ch = (char) getIntAndCheck("code", charElement);
// retrieve optional float attributes
float[] metrics = new float[4];
metrics[DefaultTeXFont.WIDTH] = getOptionalFloat("width", charElement, 0);
metrics[DefaultTeXFont.HEIGHT] = getOptionalFloat("height", charElement, 0);
metrics[DefaultTeXFont.DEPTH] = getOptionalFloat("depth", charElement, 0);
metrics[DefaultTeXFont.IT] = getOptionalFloat("italic", charElement, 0);
// set metrics
info.setMetrics(ch, metrics);
// process children
NodeList list = charElement.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (node.getNodeType() != Node.TEXT_NODE) {
Element el = (Element)node;
Object parser = charChildParsers.get(el.getTagName());
if (parser == null) // unknown element
throw new XMLResourceParseException(RESOURCE_NAME
+ ": a <Char>-element has an unknown child element '"
+ el.getTagName() + "'!");
else
// process the child element
((CharChildParser) parser).parse(el, ch, info);
}
}
}
public static void registerFonts(boolean b) {
shouldRegisterFonts = b;
}
public static Font createFont(String name) throws ResourceParseException {
return createFont(DefaultTeXFontParser.class.getResourceAsStream(name), name);
}
public static Font createFont(InputStream fontIn, String name) throws ResourceParseException {
try {
Font f = Font.createFont(Font.TRUETYPE_FONT, fontIn).deriveFont(TeXFormula.PIXELS_PER_POINT);
GraphicsEnvironment graphicEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
/**
* The following fails under java 1.5
* graphicEnv.registerFont(f);
* dynamic load then
*/
if (shouldRegisterFonts) {
try {
Method registerFontMethod = graphicEnv.getClass().getMethod("registerFont", new Class[] { Font.class });
if ((Boolean) registerFontMethod.invoke(graphicEnv, new Object[] { f }) == Boolean.FALSE) {
System.err.println("Cannot register the font " + f.getFontName());
}
} catch (Exception ex) {
if (!registerFontExceptionDisplayed) {
System.err.println("Warning: Jlatexmath: Could not access to registerFont. Please update to java 6");
registerFontExceptionDisplayed = true;
}
}
}
return f;
} catch (Exception e) {
throw new XMLResourceParseException(RESOURCE_NAME
+ ": error reading font '" + name + "'. Error message: "
+ e.getMessage());
} finally {
try {
if (fontIn != null)
fontIn.close();
} catch (IOException ioex) {
throw new RuntimeException("Close threw exception", ioex);
}
}
}
public Map<String,CharFont> parseSymbolMappings() throws ResourceParseException {
Map<String,CharFont> res = new HashMap<String,CharFont>();
Element symbolMappings = (Element)root.getElementsByTagName("SymbolMappings").item(0);
if (symbolMappings == null)
// "SymbolMappings" is required!
throw new XMLResourceParseException(RESOURCE_NAME, "SymbolMappings");
else { // element present
// iterate all mappings
NodeList list = symbolMappings.getElementsByTagName("Mapping");
for (int i = 0; i < list.getLength(); i++) {
String include = getAttrValueAndCheckIfNotNull("include", (Element)list.item(i));
Element map;
try {
if (base == null) {
map = factory.newDocumentBuilder().parse(DefaultTeXFontParser.class.getResourceAsStream(include)).getDocumentElement();
} else {
map = factory.newDocumentBuilder().parse(base.getClass().getResourceAsStream(include)).getDocumentElement();
}
} catch (Exception e) {
throw new XMLResourceParseException("Cannot find the file " + include + "!");
}
NodeList listM = map.getElementsByTagName(SYMBOL_MAPPING_EL);
for (int j = 0; j < listM.getLength(); j++) {
Element mapping = (Element)listM.item(j);
// get string attribute
String symbolName = getAttrValueAndCheckIfNotNull("name", mapping);
// get integer attributes
int ch = getIntAndCheck("ch", mapping);
String fontId = getAttrValueAndCheckIfNotNull("fontId", mapping);
// put mapping in table
String boldFontId = null;
try {
boldFontId = getAttrValueAndCheckIfNotNull("boldId", mapping);
}
catch (ResourceParseException e) {}
if (boldFontId == null) {
res.put(symbolName, new CharFont((char) ch, Font_ID.indexOf(fontId)));
} else {
res.put(symbolName, new CharFont((char) ch, Font_ID.indexOf(fontId), Font_ID.indexOf(boldFontId)));
}
}
}
return res;
}
}
public String[] parseDefaultTextStyleMappings()
throws ResourceParseException {
String[] res = new String[4];
Element defaultTextStyleMappings = (Element)root
.getElementsByTagName("DefaultTextStyleMapping").item(0);
if (defaultTextStyleMappings == null)
return res;
else { // element present
// iterate all mappings
NodeList list = defaultTextStyleMappings.getElementsByTagName("MapStyle");
for (int i = 0; i < list.getLength(); i++) {
Element mapping = (Element)list.item(i);
// get range name and check if it's valid
String code = getAttrValueAndCheckIfNotNull("code", mapping);
Object codeMapping = rangeTypeMappings.get(code);
if (codeMapping == null) // unknown range name
throw new XMLResourceParseException(RESOURCE_NAME, "MapStyle",
"code", "contains an unknown \"range name\" '" + code
+ "'!");
// get mapped style and check if it exists
String textStyleName = getAttrValueAndCheckIfNotNull("textStyle",
mapping);
Object styleMapping = parsedTextStyles.get(textStyleName);
if (styleMapping == null) // unknown text style
throw new XMLResourceParseException(RESOURCE_NAME, "MapStyle",
"textStyle", "contains an unknown text style '"
+ textStyleName + "'!");
// now check if the range is defined within the mapped text style
CharFont[] charFonts = parsedTextStyles.get(textStyleName);
int index = ((Integer) codeMapping).intValue();
if (charFonts[index] == null) // range not defined
throw new XMLResourceParseException(RESOURCE_NAME
+ ": the default text style mapping '" + textStyleName
+ "' for the range '" + code
+ "' contains no mapping for that range!");
else
// everything OK, put mapping in table
res[index] = textStyleName;
}
}
return res;
}
public Map<String,Float> parseParameters() throws ResourceParseException {
Map<String,Float> res = new HashMap<String,Float>();
Element parameters = (Element)root.getElementsByTagName("Parameters").item(0);
if (parameters == null)
// "Parameters" is required!
throw new XMLResourceParseException(RESOURCE_NAME, "Parameters");
else { // element present
// iterate all attributes
NamedNodeMap list = parameters.getAttributes();
for (int i = 0; i < list.getLength(); i++) {
String name = ((Attr)list.item(i)).getName();
// set float value (if valid)
res.put(name, new Float(getFloatAndCheck(name, parameters)));
}
return res;
}
}
public Map<String,Number> parseGeneralSettings() throws ResourceParseException {
Map <String,Number>res = new HashMap<String,Number>();
// TODO: must this be 'Number' ?
Element generalSettings = (Element)root.getElementsByTagName("GeneralSettings").item(0);
if (generalSettings == null)
// "GeneralSettings" is required!
throw new XMLResourceParseException(RESOURCE_NAME, "GeneralSettings");
else { // element present
// set required int values (if valid)
res.put(MUFONTID_ATTR, Font_ID.indexOf(getAttrValueAndCheckIfNotNull(MUFONTID_ATTR, generalSettings))); // autoboxing
res.put(SPACEFONTID_ATTR, Font_ID.indexOf(getAttrValueAndCheckIfNotNull(SPACEFONTID_ATTR, generalSettings))); // autoboxing
// set required float values (if valid)
res.put("scriptfactor", getFloatAndCheck("scriptfactor",
generalSettings)); // autoboxing
res.put("scriptscriptfactor", getFloatAndCheck(
"scriptscriptfactor", generalSettings)); // autoboxing
}
return res;
}
public Map<String,CharFont[]> parseTextStyleMappings() {
return parsedTextStyles;
}
private Map<String,CharFont[]> parseStyleMappings() throws ResourceParseException {
Map<String,CharFont[]> res = new HashMap<String,CharFont[]>();
Element textStyleMappings = (Element)root.getElementsByTagName("TextStyleMappings").item(0);
if (textStyleMappings == null)
return res;
else { // element present
// iterate all mappings
NodeList list = textStyleMappings.getElementsByTagName(STYLE_MAPPING_EL);
for (int i = 0; i < list.getLength(); i++) {
Element mapping = (Element)list.item(i);
// get required string attribute
String textStyleName = getAttrValueAndCheckIfNotNull("name",
mapping);
String boldFontId = null;
try {
boldFontId = getAttrValueAndCheckIfNotNull("bold", mapping);
}
catch (ResourceParseException e) {}
NodeList mapRangeList = mapping.getElementsByTagName("MapRange");
// iterate all mapping ranges
CharFont[] charFonts = new CharFont[4];
for (int j = 0; j < mapRangeList.getLength(); j++) {
Element mapRange = (Element)mapRangeList.item(j);
// get required integer attributes
String fontId = getAttrValueAndCheckIfNotNull("fontId", mapRange);
int ch = getIntAndCheck("start", mapRange);
// get required string attribute and check if it's a known range
String code = getAttrValueAndCheckIfNotNull("code", mapRange);
Object codeMapping = rangeTypeMappings.get(code);
if (codeMapping == null)
throw new XMLResourceParseException(RESOURCE_NAME,
"MapRange", "code",
"contains an unknown \"range name\" '" + code + "'!");
else if (boldFontId == null)
charFonts[((Integer) codeMapping).intValue()] = new CharFont((char) ch, Font_ID.indexOf(fontId));
else charFonts[((Integer) codeMapping).intValue()] = new CharFont((char) ch, Font_ID.indexOf(fontId), Font_ID.indexOf(boldFontId));
}
res.put(textStyleName, charFonts);
}
}
return res;
}
private static void setRangeTypeMappings() {
rangeTypeMappings.put("numbers", DefaultTeXFont.NUMBERS); // autoboxing
rangeTypeMappings.put("capitals", DefaultTeXFont.CAPITALS); // autoboxing
rangeTypeMappings.put("small", DefaultTeXFont.SMALL); // autoboxing
rangeTypeMappings.put("unicode", DefaultTeXFont.UNICODE); // autoboxing
}
private static String getAttrValueAndCheckIfNotNull(String attrName,
Element element) throws ResourceParseException {
String attrValue = element.getAttribute(attrName);
if (attrValue.equals(""))
throw new XMLResourceParseException(RESOURCE_NAME, element.getTagName(),
attrName, null);
return attrValue;
}
public static float getFloatAndCheck(String attrName, Element element)
throws ResourceParseException {
String attrValue = getAttrValueAndCheckIfNotNull(attrName, element);
// try parsing string to float value
float res = 0;
try {
res = (float) Double.parseDouble(attrValue);
} catch (NumberFormatException e) {
throw new XMLResourceParseException(RESOURCE_NAME, element.getTagName(),
attrName, "has an invalid real value!");
}
// parsing OK
return res;
}
public static int getIntAndCheck(String attrName, Element element)
throws ResourceParseException {
String attrValue = getAttrValueAndCheckIfNotNull(attrName, element);
// try parsing string to integer value
int res = 0;
try {
res = Integer.parseInt(attrValue);
} catch (NumberFormatException e) {
throw new XMLResourceParseException(RESOURCE_NAME, element.getTagName(),
attrName, "has an invalid integer value!");
}
// parsing OK
return res;
}
public static int getOptionalInt(String attrName, Element element,
int defaultValue) throws ResourceParseException {
String attrValue = element.getAttribute(attrName);
if (attrValue.equals("")) // attribute not present
return defaultValue;
else {
// try parsing string to integer value
int res = 0;
try {
res = Integer.parseInt(attrValue);
} catch (NumberFormatException e) {
throw new XMLResourceParseException(RESOURCE_NAME, element
.getTagName(), attrName, "has an invalid integer value!");
}
// parsing OK
return res;
}
}
public static float getOptionalFloat(String attrName, Element element,
float defaultValue) throws ResourceParseException {
String attrValue = element.getAttribute(attrName);
if (attrValue.equals("")) // attribute not present
return defaultValue;
else {
// try parsing string to float value
float res = 0;
try {
res = (float) Double.parseDouble(attrValue);
} catch (NumberFormatException e) {
throw new XMLResourceParseException(RESOURCE_NAME, element
.getTagName(), attrName, "has an invalid float value!");
}
// parsing OK
return res;
}
}
}