/* 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 com.himamis.retex.renderer.share;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.himamis.retex.renderer.share.exception.FontAlreadyLoadedException;
import com.himamis.retex.renderer.share.exception.ResourceParseException;
import com.himamis.retex.renderer.share.exception.XMLResourceParseException;
import com.himamis.retex.renderer.share.platform.FontAdapter;
import com.himamis.retex.renderer.share.platform.ParserAdapter;
import com.himamis.retex.renderer.share.platform.Resource;
import com.himamis.retex.renderer.share.platform.font.Font;
import com.himamis.retex.renderer.share.platform.parser.Element;
import com.himamis.retex.renderer.share.platform.parser.NamedNodeMap;
import com.himamis.retex.renderer.share.platform.parser.Node;
import com.himamis.retex.renderer.share.platform.parser.NodeList;
/**
* 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 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
}
@Override
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
}
@Override
public void parse(Element el, char ch, FontInfo info) throws ResourceParseException {
// get required integer attribute
int code = DefaultTeXFontParser.getIntAndCheck("code", el);
// get required double attribute
double 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
}
@Override
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
}
@Override
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();
}
private final Resource resource;
private final ParserAdapter parserAdapter;
public DefaultTeXFontParser() throws ResourceParseException {
resource = new Resource();
parserAdapter = new ParserAdapter();
Object file = resource.loadResource(DefaultTeXFontParser.class, RESOURCE_NAME);
try {
root = parserAdapter.createParserAndParseFile(file, true, true);
} catch (Exception e) { // JDOMException or IOException
throw new XMLResourceParseException(RESOURCE_NAME, e);
}
}
public DefaultTeXFontParser(Object file, String name) throws ResourceParseException {
resource = new Resource();
parserAdapter = new ParserAdapter();
try {
root = parserAdapter.createParserAndParseFile(file, true, true);
} catch (Exception e) { // JDOMException or IOException
throw new XMLResourceParseException(name, e);
}
}
public DefaultTeXFontParser(Object base, Object file, String name) throws ResourceParseException {
this.base = base;
resource = new Resource();
parserAdapter = new ParserAdapter();
try {
root = parserAdapter.createParserAndParseFile(file, true, true);
} 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, Object file, String name)
throws ResourceParseException {
if (file == null) {
return fi;
}
ArrayList<FontInfo> res = new ArrayList<FontInfo>(Arrays.asList(fi));
Element font;
try {
font = parserAdapter.createParserAndParseFile(file);
} 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
double space = getFloatAndCheck("space", font);
double xHeight = getFloatAndCheck("xHeight", font);
double 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,
unicode, xHeight, space,
quad, bold, roman, ss, tt, it);
if (skewChar != -1) {
info.setSkewChar((char) skewChar);
}
// process all "Char"-elements
NodeList listF = font.getElementsByTagName("Char");
for (int j = 0; j < listF.getLength(); j++) {
processCharElement(listF.item(j).castToElement(), 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[] fontInfo)
throws ResourceParseException {
FontInfo[] fi = fontInfo;
Element fontDescriptions = root.getElementsByTagName("FontDescriptions").item(0).castToElement();
if (!fontDescriptions.isNull()) { // element present
NodeList list = fontDescriptions.getElementsByTagName("Metrics");
for (int i = 0; i < list.getLength(); i++) {
// get required string attribute
String include = getAttrValueAndCheckIfNotNull("include", list.item(i).castToElement());
if (base == null) {
fi = parseFontDescriptions(fi,
resource.loadResource(DefaultTeXFontParser.class, include), include);
} else {
fi = parseFontDescriptions(fi, resource.loadResource(base, include), include);
}
}
}
return fi;
}
protected void parseExtraPath() throws ResourceParseException {
Element syms = root.getElementsByTagName("TeXSymbols").item(0).castToElement();
if (!syms.isNull()) { // element present
// get required string attribute
String include = getAttrValueAndCheckIfNotNull("include", syms);
SymbolAtom.addSymbolAtom(resource.loadResource(base, include), include);
}
Element settings = root.getElementsByTagName("FormulaSettings").item(0).castToElement();
if (!settings.isNull()) { // element present
// get required string attribute
String include = getAttrValueAndCheckIfNotNull("include", settings);
TeXFormula.addSymbolMappings(resource.loadResource(base, include), include);
}
}
private static void processCharElement(Element charElement, FontInfo info) throws ResourceParseException {
// retrieve required integer attribute
char ch = (char) getIntAndCheck("code", charElement);
// retrieve optional double attributes
double[] metrics = new double[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 = node.castToElement();
Object parser = charChildParsers.get(el.getTagName());
if (parser == null) {
throw new XMLResourceParseException(RESOURCE_NAME
+ ": a <Char>-element has an unknown child element '" + el.getTagName() + "'!");
}
// 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(null, name);
}
public static Font createFont(Object base, String name) throws ResourceParseException {
FontAdapter fontAdapter = new FontAdapter();
return fontAdapter.loadFont(base, name);
}
public Map<String, CharFont> parseSymbolMappings() throws ResourceParseException {
Map<String, CharFont> res = new HashMap<String, CharFont>();
Element symbolMappings = root.getElementsByTagName("SymbolMappings").item(0).castToElement();
if (symbolMappings.isNull()) {
// "SymbolMappings" is required!
throw new XMLResourceParseException(RESOURCE_NAME, "SymbolMappings");
}
// iterate all mappings
NodeList list = symbolMappings.getElementsByTagName("Mapping");
for (int i = 0; i < list.getLength(); i++) {
String include = getAttrValueAndCheckIfNotNull("include",
list.item(i).castToElement());
Element map;
try {
if (base == null) {
map = parserAdapter.createParserAndParseFile(resource
.loadResource(DefaultTeXFontParser.class, include));
} else {
map = parserAdapter.createParserAndParseFile(
resource.loadResource(base, include));
}
} 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 = listM.item(j).castToElement();
// 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 = root.getElementsByTagName("DefaultTextStyleMapping").item(0)
.castToElement();
if (defaultTextStyleMappings.isNull()) {
return res;
}
// iterate all mappings
NodeList list = defaultTextStyleMappings
.getElementsByTagName("MapStyle");
for (int i = 0; i < list.getLength(); i++) {
Element mapping = list.item(i).castToElement();
// get range name and check if it's valid
String code = getAttrValueAndCheckIfNotNull("code", mapping);
Object codeMapping = rangeTypeMappings.get(code);
if (codeMapping == null) {
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) {
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) {
throw new XMLResourceParseException(
RESOURCE_NAME + ": the default text style mapping '"
+ textStyleName + "' for the range '" + code
+ "' contains no mapping for that range!");
}
// everything OK, put mapping in table
res[index] = textStyleName;
}
return res;
}
public Map<String, Double> parseParameters() throws ResourceParseException {
Map<String, Double> res = new HashMap<String, Double>();
Element parameters = root.getElementsByTagName("Parameters").item(0).castToElement();
if (parameters.isNull()) {
// "Parameters" is required!
throw new XMLResourceParseException(RESOURCE_NAME, "Parameters");
}
// iterate all attributes
NamedNodeMap list = parameters.getAttributes();
for (int i = 0; i < list.getLength(); i++) {
String name = (list.item(i).castToAttr()).getName();
// set double value (if valid)
res.put(name, new Double(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 = root.getElementsByTagName("GeneralSettings").item(0).castToElement();
if (generalSettings.isNull()) {
// "GeneralSettings" is required!
throw new XMLResourceParseException(RESOURCE_NAME, "GeneralSettings");
}
// 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 double 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 = root.getElementsByTagName("TextStyleMappings").item(0).castToElement();
if (textStyleMappings.isNull()) {
return res;
}
// iterate all mappings
NodeList list = textStyleMappings
.getElementsByTagName(STYLE_MAPPING_EL);
for (int i = 0; i < list.getLength(); i++) {
Element mapping = list.item(i).castToElement();
// 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 = mapRangeList.item(j).castToElement();
// 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 ("".equals(attrValue)) {
throw new XMLResourceParseException(RESOURCE_NAME, element.getTagName(), attrName, null);
}
return attrValue;
}
public static double getFloatAndCheck(String attrName, Element element) throws ResourceParseException {
String attrValue = getAttrValueAndCheckIfNotNull(attrName, element);
// try parsing string to double value
double res = 0;
try {
res = 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 ("".equals(attrValue)) {
return defaultValue;
}
// 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 double getOptionalFloat(String attrName, Element element, double defaultValue)
throws ResourceParseException {
String attrValue = element.getAttribute(attrName);
if ("".equals(attrValue)) {
return defaultValue;
}
// try parsing string to double value
double res = 0;
try {
res = Double.parseDouble(attrValue);
} catch (NumberFormatException e) {
throw new XMLResourceParseException(RESOURCE_NAME,
element.getTagName(), attrName,
"has an invalid double value!");
}
// parsing OK
return res;
}
}