/*
 * 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: TTFReader.java 1805173 2017-08-16 10:50:04Z ssteiner $ */

package org.apache.fop.fonts.apps;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import org.apache.fop.Version;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.FontUtil;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OFFontLoader;
import org.apache.fop.fonts.truetype.TTFFile;

A tool which reads TTF files and generates XML font metrics file for use in FOP.
/** * 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
/** 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
/** Current version number for the metrics file */
public static final int METRICS_VERSION = 2;
Main constructor.
/** * 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.
Params:
  • 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
/** * 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); 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.
Params:
  • fileName – The filename of the TTF file.
  • fontName – The name of the font
  • useKerning – true if should load kerning data
  • useAdvanced – true if should load advanced typographic table data
Throws:
Returns:The TTF as an object, null if the font is incompatible.
/** * 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 + "..."); InputStream stream = new FileInputStream(fileName); try { FontFileReader reader = new FontFileReader(stream); String header = OFFontLoader.readHeader(reader); boolean supported = ttfFile.readFont(reader, header, fontName); if (!supported) { return null; } } finally { stream.close(); } 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.
Params:
  • ttf – The PFM file to generate the font metrics from.
  • fontName – Name of the font
  • className – Class name for the font
  • resource – path to the font as embedded resource
  • file – path to the font as file
  • isCid – True if the font is CID encoded
  • ttcName – Name of the TrueType Collection
Returns:The DOM document representing the font metrics file.
/** * 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<String> familyNames = ttf.getFamilyNames(); if (familyNames.size() > 0) { String familyName = 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); for (CMapSegment ce : ttf.getCMaps()) { 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])); int[] bbox = ttf.getBBox(i); wxel.setAttribute("xMin", String.valueOf(bbox[0])); wxel.setAttribute("yMin", String.valueOf(bbox[1])); wxel.setAttribute("xMax", String.valueOf(bbox[2])); wxel.setAttribute("yMax", String.valueOf(bbox[3])); 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 Set<Integer> kerningKeys; if (isCid) { kerningKeys = ttf.getKerning().keySet(); } else { kerningKeys = ttf.getAnsiKerning().keySet(); } for (Integer kpx1 : kerningKeys) { el = doc.createElement("kerning"); el.setAttribute("kpx1", kpx1.toString()); parent.appendChild(el); Element el2 = null; Map<Integer, Integer> h2; if (isCid) { h2 = ttf.getKerning().get(kpx1); } else { h2 = ttf.getAnsiKerning().get(kpx1); } for (Map.Entry<Integer, Integer> e : h2.entrySet()) { Integer kpx2 = e.getKey(); if (isCid || kpx2 < 256) { el2 = doc.createElement("pair"); el2.setAttribute("kpx2", kpx2.toString()); Integer val = e.getValue(); el2.setAttribute("kern", val.toString()); el.appendChild(el2); } } } }
Bugzilla 40739, check that attr has a metrics-version attribute compatible with ours.
Params:
  • attr – attributes read from the root element of a metrics XML file
Throws:
/** * 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." ); } } }