/*****************************************************************************
* Copyrightp (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.bridge;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.dom.svg.XMLBaseSupport;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.gvt.font.GVTFontFamily;
import org.apache.batik.gvt.font.UnresolvedFontFamily;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Utility class for SVG fonts.
*
* @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a>
* @version $Id: SVGFontUtilities.java,v 1.13 2003/04/11 13:54:49 vhardy Exp $
*/
public abstract class SVGFontUtilities implements SVGConstants {
/**
* Given a font family name tries to find a matching SVG font
* object. If finds one, returns an SVGFontFamily otherwise
* returns an UnresolvedFontFamily.
*
* @param textElement The text element that the font family will
* be attached to.
* @param ctx The bridge context, used to search for a matching
* SVG font element.
* @param fontFamilyName The name of the font family to search
* for.
* @param fontWeight The weight of the font to use when trying to
* match an SVG font family.
* @param fontStyle The style of the font to use when trying to
* match as SVG font family.
*
* @return A GVTFontFamily for the specified font attributes. This
* will be unresolved unless a matching SVG font was found.
*/
public static GVTFontFamily getFontFamily(Element textElement,
BridgeContext ctx,
String fontFamilyName,
String fontWeight,
String fontStyle) {
// TODO: should match against font-variant as well
String fontKeyName = fontFamilyName.toLowerCase() + " " +
fontWeight + " " + fontStyle;
// check fontFamilyMap to see if we have already created an
// FontFamily that matches
Map fontFamilyMap = ctx.getFontFamilyMap();
GVTFontFamily fontFamily =
(GVTFontFamily)fontFamilyMap.get(fontKeyName);
if (fontFamily != null) {
return fontFamily;
}
// try to find a matching SVGFontFace element
Document doc = textElement.getOwnerDocument();
NodeList fontFaceElements = doc.getElementsByTagNameNS
(SVG_NAMESPACE_URI, SVG_FONT_FACE_TAG);
Vector svgFontFamilies = new Vector();
for (int i = 0; i < fontFaceElements.getLength(); i++) {
Element fontFaceElement = (Element)fontFaceElements.item(i);
// find matching font element
Element fontElement = findFontElement(fontFaceElement,
fontFamilyName,
ctx);
if (fontElement != null) {
// create a font face
Element fontFaceChild = null;
for (Node n = fontElement.getFirstChild();
n != null;
n = n.getNextSibling()) {
if (n.getNodeType() == n.ELEMENT_NODE) {
if (n.getNamespaceURI().equals(SVG_NAMESPACE_URI) &&
n.getLocalName().equals(SVG_FONT_FACE_TAG)) {
fontFaceChild = (Element)n;
break;
}
}
}
if (fontFaceChild == null) {
continue;
}
SVGFontFaceElementBridge fontFaceBridge =
(SVGFontFaceElementBridge)ctx.getBridge(fontFaceChild);
SVGFontFace fontFace =
fontFaceBridge.createFontFace(ctx, fontFaceChild);
// see if the font face is ok for the font-weight and style etc
String fontFaceStyle = fontFace.getFontStyle();
if (fontFaceStyle.equals(SVG_ALL_VALUE) ||
fontFaceStyle.indexOf(fontStyle) != -1) {
// create a new SVGFontFamily
GVTFontFamily gvtFontFamily =
new SVGFontFamily(fontFace, fontElement, ctx);
svgFontFamilies.add(gvtFontFamily);
}
}
}
if (svgFontFamilies.size() == 1) {
// only found one matching svg font family
fontFamilyMap.put(fontKeyName, svgFontFamilies.elementAt(0));
return (GVTFontFamily)svgFontFamilies.elementAt(0);
} else if (svgFontFamilies.size() > 1) {
// need to find font face that matches the font-weight closest
String fontWeightNumber = getFontWeightNumberString(fontWeight);
// create lists of font weight numbers for each font family
Vector fontFamilyWeights = new Vector();
for (int i = 0; i < svgFontFamilies.size(); i++) {
SVGFontFace fontFace =
((SVGFontFamily)svgFontFamilies.get(i)).getFontFace();
String fontFaceWeight = fontFace.getFontWeight();
fontFaceWeight = getFontWeightNumberString(fontFaceWeight);
fontFamilyWeights.add(fontFaceWeight);
}
// make sure that each possible font-weight has been
// assigned to a font-face, if not then need to "fill the
// holes"
Vector newFontFamilyWeights = (Vector)fontFamilyWeights.clone();
for (int i = 100; i <= 900; i+= 100) {
String weightString = String.valueOf(i);
boolean matched = false;
int minDifference = 1000;
int minDifferenceIndex = 0;
for (int j = 0; j < fontFamilyWeights.size(); j++) {
String fontFamilyWeight = (String)fontFamilyWeights.get(j);
if (fontFamilyWeight.indexOf(weightString) > -1) {
matched = true;
break;
}
StringTokenizer st =
new StringTokenizer(fontFamilyWeight, " ,");
while (st.hasMoreTokens()) {
int weightNum = Integer.parseInt(st.nextToken());
int difference = (int)Math.abs(weightNum - i);
if (difference < minDifference) {
minDifference = difference;
minDifferenceIndex = j;
}
}
}
if (!matched) {
String newFontFamilyWeight =
newFontFamilyWeights.elementAt(minDifferenceIndex) +
", " + weightString;
newFontFamilyWeights.setElementAt
(newFontFamilyWeight, minDifferenceIndex);
}
}
// now find matching font weight
for (int i = 0; i < svgFontFamilies.size(); i++) {
String fontFaceWeight = (String)newFontFamilyWeights.get(i);
if (fontFaceWeight.indexOf(fontWeightNumber) > -1) {
fontFamilyMap.put(fontKeyName, svgFontFamilies.get(i));
return (GVTFontFamily)svgFontFamilies.elementAt(i);
}
}
// should not get here, just return the first svg font family
fontFamilyMap.put(fontKeyName, svgFontFamilies.elementAt(0));
return (GVTFontFamily) svgFontFamilies.elementAt(0);
} else {
// couldn't find one so return an UnresolvedFontFamily object
GVTFontFamily gvtFontFamily =
new UnresolvedFontFamily(fontFamilyName);
fontFamilyMap.put(fontKeyName, gvtFontFamily);
return gvtFontFamily;
}
}
/**
* Returns a string that contains all of the font weight numbers for the
* specified font weight attribute value.
*
* @param fontWeight The font-weight attribute value.
*
* @return The font weight expressed as font weight numbers.
* e.g. "normal" becomes "400".
*/
protected static String getFontWeightNumberString(String fontWeight) {
if (fontWeight.equals(SVG_NORMAL_VALUE)) {
return SVG_400_VALUE;
} else if (fontWeight.equals(SVG_BOLD_VALUE)) {
return SVG_700_VALUE;
} else if (fontWeight.equals(SVG_ALL_VALUE)) {
return "100, 200, 300, 400, 500, 600, 700, 800, 900";
}
return fontWeight;
}
/**
* Finds the font element corresponding to the given font-face element.
*/
protected static Element findFontElement(Element ffElt, String family,
BridgeContext ctx) {
String ffname = ffElt.getAttributeNS(null, SVG_FONT_FAMILY_ATTRIBUTE);
if (ffname.length() < family.length()) {
return null;
}
ffname = ffname.toLowerCase();
int idx = ffname.indexOf(family.toLowerCase());
if (idx == -1) {
return null;
}
// see if the family name is not the part of a bigger family name.
if (ffname.length() > family.length()) {
boolean quote = false;
if (idx > 0) {
char c = ffname.charAt(idx - 1);
switch (c) {
default:
return null;
case ' ':
loop: for (int i = idx - 2; i >= 0; --i) {
switch (ffname.charAt(i)) {
default:
return null;
case ' ':
continue;
case '"':
case '\'':
quote = true;
break loop;
}
}
break;
case '"':
case '\'':
quote = true;
case ',':
}
}
if (idx + family.length() < ffname.length()) {
char c = ffname.charAt(idx + family.length());
switch (c) {
default:
return null;
case ' ':
loop: for (int i = idx + family.length() + 1;
i < ffname.length(); i++) {
switch (ffname.charAt(i)) {
default:
return null;
case ' ':
continue;
case '"':
case '\'':
if (!quote) {
return null;
}
break loop;
}
}
break;
case '"':
case '\'':
if (!quote) {
return null;
}
case ',':
}
}
}
Element fontElt = SVGUtilities.getParentElement(ffElt);
if (fontElt.getNamespaceURI() == SVG_NAMESPACE_URI &&
fontElt.getLocalName().equals(SVG_FONT_TAG)) {
return fontElt;
}
// Search for a font-face-src element
Element ffsrc = null;
for (Node n = ffElt.getFirstChild();
n != null;
n = n.getNextSibling()) {
if (n.getNodeType() == n.ELEMENT_NODE) {
if (n.getNamespaceURI().equals(SVG_NAMESPACE_URI) &&
n.getLocalName().equals(SVG_FONT_FACE_SRC_TAG)) {
ffsrc = (Element)n;
break;
}
}
}
if (ffsrc == null) {
return null;
}
// Search for a font-face-uri element
Element ffuri = null;
for (Node n = ffsrc.getFirstChild();
n != null;
n = n.getNextSibling()) {
if (n.getNodeType() == n.ELEMENT_NODE) {
if (n.getNamespaceURI().equals(SVG_NAMESPACE_URI) &&
n.getLocalName().equals(SVG_FONT_FACE_URI_TAG)) {
ffuri = (Element)n;
break;
}
}
}
if (ffuri == null) {
return null;
}
String uri = XLinkSupport.getXLinkHref(ffuri);
Element ref = ctx.getReferencedElement(ffuri, uri);
if (ref.getNamespaceURI() != SVG_NAMESPACE_URI ||
ref.getLocalName() != SVG_FONT_TAG) {
return null;
}
SVGOMDocument doc = (SVGOMDocument)ffuri.getOwnerDocument();
SVGOMDocument rdoc = (SVGOMDocument)ref.getOwnerDocument();
boolean isLocal = doc == rdoc;
fontElt = (isLocal) ? ref : (Element)doc.importNode(ref, true);
if (!isLocal) {
String base = XMLBaseSupport.getCascadedXMLBase(ffuri);
Element g = doc.createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
g.appendChild(fontElt);
g.setAttributeNS(XMLBaseSupport.XML_NAMESPACE_URI,
"xml:base",
base);
CSSUtilities.computeStyleAndURIs(ref, fontElt, uri);
}
return fontElt;
}
}