blob: 1804a8dd964b5e5c358de34a90b5787aebad85ad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.fonts.apps;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.Version;
import org.apache.fop.fonts.FontUtil;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFCmapEntry;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.util.CommandLineLogger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
/**
* A tool which reads TTF files and generates
* XML font metrics file for use in FOP.
*/
public class TTFReader extends AbstractFontReader {
/** Used to detect incompatible versions of the generated XML files */
public static final String METRICS_VERSION_ATTR = "metrics-version";
/** Current version number for the metrics file */
public static final int METRICS_VERSION = 2;
/**
* Main constructor.
*/
public TTFReader() {
super();
}
private static void displayUsage() {
System.out.println(
"java " + TTFReader.class.getName() + " [options] fontfile.ttf xmlfile.xml");
System.out.println();
System.out.println("where options can be:");
System.out.println("-t Trace mode");
System.out.println("-d Debug mode");
System.out.println("-q Quiet mode");
System.out.println("-enc ansi");
System.out.println(" With this option you create a WinAnsi encoded font.");
System.out.println(" The default is to create a CID keyed font.");
System.out.println(" If you're not going to use characters outside the");
System.out.println(" pdfencoding range (almost the same as iso-8889-1)");
System.out.println(" you can add this option.");
System.out.println("-ttcname <fontname>");
System.out.println(" If you're reading data from a TrueType Collection");
System.out.println(" (.ttc file) you must specify which font from the");
System.out.println(" collection you will read metrics from. If you read");
System.out.println(" from a .ttc file without this option, the fontnames");
System.out.println(" will be listed for you.");
System.out.println(" -fn <fontname>");
System.out.println(" default is to use the fontname in the .ttf file, but");
System.out.println(" you can override that name to make sure that the");
System.out.println(" embedded font is used (if you're embedding fonts)");
System.out.println(" instead of installed fonts when viewing documents ");
System.out.println(" with Acrobat Reader.");
}
/**
* The main method for the TTFReader tool.
*
* @param args Command-line arguments: [options] fontfile.ttf xmlfile.xml
* where options can be:
* -fn <fontname>
* default is to use the fontname in the .ttf file, but you can override
* that name to make sure that the embedded font is used instead of installed
* fonts when viewing documents with Acrobat Reader.
* -cn <classname>
* default is to use the fontname
* -ef <path to the truetype fontfile>
* will add the possibility to embed the font. When running fop, fop will look
* for this file to embed it
* -er <path to truetype fontfile relative to org/apache/fop/render/pdf/fonts>
* you can also include the fontfile in the fop.jar file when building fop.
* You can use both -ef and -er. The file specified in -ef will be searched first,
* then the -er file.
* -nocs
* if complex script features are disabled
*/
public static void main(String[] args) {
String embFile = null;
String embResource = null;
String className = null;
String fontName = null;
String ttcName = null;
boolean isCid = true;
Map options = new java.util.HashMap();
String[] arguments = parseArguments(options, args);
// Enable the simple command line logging when no other logger is
// defined.
LogFactory logFactory = LogFactory.getFactory();
if (System.getProperty("org.apache.commons.logging.Log") == null) {
logFactory.setAttribute("org.apache.commons.logging.Log",
CommandLineLogger.class.getName());
}
determineLogLevel(options);
TTFReader app = new TTFReader();
log.info("TTF Reader for Apache FOP " + Version.getVersion() + "\n");
if (options.get("-enc") != null) {
String enc = (String)options.get("-enc");
if ("ansi".equals(enc)) {
isCid = false;
}
}
if (options.get("-ttcname") != null) {
ttcName = (String)options.get("-ttcname");
}
if (options.get("-ef") != null) {
embFile = (String)options.get("-ef");
}
if (options.get("-er") != null) {
embResource = (String)options.get("-er");
}
if (options.get("-fn") != null) {
fontName = (String)options.get("-fn");
}
if (options.get("-cn") != null) {
className = (String)options.get("-cn");
}
boolean useKerning = true;
boolean useAdvanced = true;
if (options.get("-nocs") != null) {
useAdvanced = false;
}
if (arguments.length != 2 || options.get("-h") != null
|| options.get("-help") != null || options.get("--help") != null) {
displayUsage();
} else {
try {
log.info("Parsing font...");
TTFFile ttf = app.loadTTF(arguments[0], ttcName, useKerning, useAdvanced);
if (ttf != null) {
org.w3c.dom.Document doc = app.constructFontXML(ttf,
fontName, className, embResource, embFile, isCid,
ttcName);
if (isCid) {
log.info("Creating CID encoded metrics...");
} else {
log.info("Creating WinAnsi encoded metrics...");
}
if (doc != null) {
app.writeFontXML(doc, arguments[1]);
}
if (ttf.isEmbeddable()) {
log.info("This font contains no embedding license restrictions.");
} else {
log.info("** Note: This font contains license retrictions for\n"
+ " embedding. This font shouldn't be embedded.");
}
}
log.info("");
log.info("XML font metrics file successfully created.");
} catch (Exception e) {
log.error("Error while building XML font metrics file.", e);
System.exit(-1);
}
}
}
/**
* Read a TTF file and returns it as an object.
*
* @param fileName The filename of the TTF file.
* @param fontName The name of the font
* @param useKerning true if should load kerning data
* @param useAdvanced true if should load advanced typographic table data
* @return The TTF as an object, null if the font is incompatible.
* @throws IOException In case of an I/O problem
*/
public TTFFile loadTTF(String fileName, String fontName, boolean useKerning, boolean useAdvanced) throws IOException {
TTFFile ttfFile = new TTFFile(useKerning, useAdvanced);
log.info("Reading " + fileName + "...");
FontFileReader reader = new FontFileReader(fileName);
boolean supported = ttfFile.readFont(reader, fontName);
if (!supported) {
return null;
}
log.info("Font Family: " + ttfFile.getFamilyNames());
if (ttfFile.isCFF()) {
throw new UnsupportedOperationException(
"OpenType fonts with CFF data are not supported, yet");
}
return ttfFile;
}
/**
* Generates the font metrics file from the TTF/TTC file.
*
* @param ttf The PFM file to generate the font metrics from.
* @param fontName Name of the font
* @param className Class name for the font
* @param resource path to the font as embedded resource
* @param file path to the font as file
* @param isCid True if the font is CID encoded
* @param ttcName Name of the TrueType Collection
* @return The DOM document representing the font metrics file.
*/
public org.w3c.dom.Document constructFontXML(TTFFile ttf,
String fontName, String className, String resource, String file,
boolean isCid, String ttcName) {
log.info("Creating xml font file...");
Document doc;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
doc = factory.newDocumentBuilder().newDocument();
} catch (javax.xml.parsers.ParserConfigurationException e) {
log.error("Can't create DOM implementation", e);
return null;
}
Element root = doc.createElement("font-metrics");
doc.appendChild(root);
root.setAttribute(METRICS_VERSION_ATTR, String.valueOf(METRICS_VERSION));
if (isCid) {
root.setAttribute("type", "TYPE0");
} else {
root.setAttribute("type", "TRUETYPE");
}
Element el = doc.createElement("font-name");
root.appendChild(el);
// Note that the PostScript name usually is something like
// "Perpetua-Bold", but the TrueType spec says that in the ttf file
// it should be "Perpetua,Bold".
String s = FontUtil.stripWhiteSpace(ttf.getPostScriptName());
if (fontName != null) {
el.appendChild(doc.createTextNode(FontUtil.stripWhiteSpace(fontName)));
} else {
el.appendChild(doc.createTextNode(s));
}
if (ttf.getFullName() != null) {
el = doc.createElement("full-name");
root.appendChild(el);
el.appendChild(doc.createTextNode(ttf.getFullName()));
}
Set familyNames = ttf.getFamilyNames();
if (familyNames.size() > 0) {
String familyName = (String)familyNames.iterator().next();
el = doc.createElement("family-name");
root.appendChild(el);
el.appendChild(doc.createTextNode(familyName));
}
el = doc.createElement("embed");
root.appendChild(el);
if (file != null && ttf.isEmbeddable()) {
el.setAttribute("file", file);
}
if (resource != null && ttf.isEmbeddable()) {
el.setAttribute("class", resource);
}
el = doc.createElement("cap-height");
root.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getCapHeight())));
el = doc.createElement("x-height");
root.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getXHeight())));
el = doc.createElement("ascender");
root.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getLowerCaseAscent())));
el = doc.createElement("descender");
root.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getLowerCaseDescent())));
Element bbox = doc.createElement("bbox");
root.appendChild(bbox);
int[] bb = ttf.getFontBBox();
final String[] names = {"left", "bottom", "right", "top"};
for (int i = 0; i < names.length; i++) {
el = doc.createElement(names[i]);
bbox.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(bb[i])));
}
el = doc.createElement("flags");
root.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getFlags())));
el = doc.createElement("stemv");
root.appendChild(el);
el.appendChild(doc.createTextNode(ttf.getStemV()));
el = doc.createElement("italicangle");
root.appendChild(el);
el.appendChild(doc.createTextNode(ttf.getItalicAngle()));
if (ttcName != null) {
el = doc.createElement("ttc-name");
root.appendChild(el);
el.appendChild(doc.createTextNode(ttcName));
}
el = doc.createElement("subtype");
root.appendChild(el);
// Fill in extras for CID keyed fonts
if (isCid) {
el.appendChild(doc.createTextNode("TYPE0"));
generateDOM4MultiByteExtras(root, ttf, isCid);
} else {
// Fill in extras for singlebyte fonts
el.appendChild(doc.createTextNode("TRUETYPE"));
generateDOM4SingleByteExtras(root, ttf, isCid);
}
generateDOM4Kerning(root, ttf, isCid);
return doc;
}
private void generateDOM4MultiByteExtras(Element parent, TTFFile ttf, boolean isCid) {
Element el;
Document doc = parent.getOwnerDocument();
Element mel = doc.createElement("multibyte-extras");
parent.appendChild(mel);
el = doc.createElement("cid-type");
mel.appendChild(el);
el.appendChild(doc.createTextNode("CIDFontType2"));
el = doc.createElement("default-width");
mel.appendChild(el);
el.appendChild(doc.createTextNode("0"));
el = doc.createElement("bfranges");
mel.appendChild(el);
Iterator iter = ttf.getCMaps().listIterator();
while (iter.hasNext()) {
TTFCmapEntry ce = (TTFCmapEntry)iter.next();
Element el2 = doc.createElement("bf");
el.appendChild(el2);
el2.setAttribute("us", String.valueOf(ce.getUnicodeStart()));
el2.setAttribute("ue", String.valueOf(ce.getUnicodeEnd()));
el2.setAttribute("gi", String.valueOf(ce.getGlyphStartIndex()));
}
el = doc.createElement("cid-widths");
el.setAttribute("start-index", "0");
mel.appendChild(el);
int[] wx = ttf.getWidths();
for (int i = 0; i < wx.length; i++) {
Element wxel = doc.createElement("wx");
wxel.setAttribute("w", String.valueOf(wx[i]));
el.appendChild(wxel);
}
}
private void generateDOM4SingleByteExtras(Element parent, TTFFile ttf, boolean isCid) {
Element el;
Document doc = parent.getOwnerDocument();
Element sel = doc.createElement("singlebyte-extras");
parent.appendChild(sel);
el = doc.createElement("encoding");
sel.appendChild(el);
el.appendChild(doc.createTextNode(ttf.getCharSetName()));
el = doc.createElement("first-char");
sel.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getFirstChar())));
el = doc.createElement("last-char");
sel.appendChild(el);
el.appendChild(doc.createTextNode(String.valueOf(ttf.getLastChar())));
Element widths = doc.createElement("widths");
sel.appendChild(widths);
for (short i = ttf.getFirstChar(); i <= ttf.getLastChar(); i++) {
el = doc.createElement("char");
widths.appendChild(el);
el.setAttribute("idx", String.valueOf(i));
el.setAttribute("wdt", String.valueOf(ttf.getCharWidth(i)));
}
}
private void generateDOM4Kerning(Element parent, TTFFile ttf, boolean isCid) {
Element el;
Document doc = parent.getOwnerDocument();
// Get kerning
Iterator iter;
if (isCid) {
iter = ttf.getKerning().keySet().iterator();
} else {
iter = ttf.getAnsiKerning().keySet().iterator();
}
while (iter.hasNext()) {
Integer kpx1 = (Integer)iter.next();
el = doc.createElement("kerning");
el.setAttribute("kpx1", kpx1.toString());
parent.appendChild(el);
Element el2 = null;
Map h2;
if (isCid) {
h2 = (Map)ttf.getKerning().get(kpx1);
} else {
h2 = (Map)ttf.getAnsiKerning().get(kpx1);
}
Iterator iter2 = h2.keySet().iterator();
while (iter2.hasNext()) {
Integer kpx2 = (Integer)iter2.next();
if (isCid || kpx2.intValue() < 256) {
el2 = doc.createElement("pair");
el2.setAttribute("kpx2", kpx2.toString());
Integer val = (Integer)h2.get(kpx2);
el2.setAttribute("kern", val.toString());
el.appendChild(el2);
}
}
}
}
/**
* Bugzilla 40739, check that attr has a metrics-version attribute
* compatible with ours.
* @param attr attributes read from the root element of a metrics XML file
* @throws SAXException if incompatible
*/
public static void checkMetricsVersion(Attributes attr) throws SAXException {
String err = null;
final String str = attr.getValue(METRICS_VERSION_ATTR);
if (str == null) {
err = "Missing " + METRICS_VERSION_ATTR + " attribute";
} else {
int version = 0;
try {
version = Integer.parseInt(str);
if (version < METRICS_VERSION) {
err = "Incompatible " + METRICS_VERSION_ATTR
+ " value (" + version + ", should be " + METRICS_VERSION
+ ")"
;
}
} catch (NumberFormatException e) {
err = "Invalid " + METRICS_VERSION_ATTR
+ " attribute value (" + str + ")";
}
}
if (err != null) {
throw new SAXException(
err
+ " - please regenerate the font metrics file with "
+ "a more recent version of FOP."
);
}
}
}