001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: TTFReader.java 542237 2007-05-28 14:31:24Z jeremias $ */
019:
020: package org.apache.fop.fonts.apps;
021:
022: import java.io.IOException;
023: import java.util.Iterator;
024: import java.util.Map;
025:
026: import javax.xml.parsers.DocumentBuilderFactory;
027:
028: import org.apache.commons.logging.LogFactory;
029: import org.apache.fop.Version;
030: import org.apache.fop.fonts.FontUtil;
031: import org.apache.fop.fonts.truetype.FontFileReader;
032: import org.apache.fop.fonts.truetype.TTFCmapEntry;
033: import org.apache.fop.fonts.truetype.TTFFile;
034: import org.apache.fop.util.CommandLineLogger;
035: import org.w3c.dom.Document;
036: import org.w3c.dom.Element;
037: import org.xml.sax.Attributes;
038: import org.xml.sax.SAXException;
039:
040: /**
041: * A tool which reads TTF files and generates
042: * XML font metrics file for use in FOP.
043: */
044: public class TTFReader extends AbstractFontReader {
045:
046: /** Used to detect incompatible versions of the generated XML files */
047: public static final String METRICS_VERSION_ATTR = "metrics-version";
048: /** Current version number for the metrics file */
049: public static final int METRICS_VERSION = 2;
050:
051: /**
052: * Main constructor.
053: */
054: public TTFReader() {
055: super ();
056: }
057:
058: private static void displayUsage() {
059: System.out.println("java " + TTFReader.class.getName()
060: + " [options] fontfile.ttf xmlfile.xml");
061: System.out.println();
062: System.out.println("where options can be:");
063: System.out.println("-d Debug mode");
064: System.out.println("-q Quiet mode");
065: System.out.println("-enc ansi");
066: System.out
067: .println(" With this option you create a WinAnsi encoded font.");
068: System.out
069: .println(" The default is to create a CID keyed font.");
070: System.out
071: .println(" If you're not going to use characters outside the");
072: System.out
073: .println(" pdfencoding range (almost the same as iso-8889-1)");
074: System.out.println(" you can add this option.");
075: System.out.println("-ttcname <fontname>");
076: System.out
077: .println(" If you're reading data from a TrueType Collection");
078: System.out
079: .println(" (.ttc file) you must specify which font from the");
080: System.out
081: .println(" collection you will read metrics from. If you read");
082: System.out
083: .println(" from a .ttc file without this option, the fontnames");
084: System.out.println(" will be listed for you.");
085: System.out.println(" -fn <fontname>");
086: System.out
087: .println(" default is to use the fontname in the .ttf file, but");
088: System.out
089: .println(" you can override that name to make sure that the");
090: System.out
091: .println(" embedded font is used (if you're embedding fonts)");
092: System.out
093: .println(" instead of installed fonts when viewing documents ");
094: System.out.println(" with Acrobat Reader.");
095: }
096:
097: /**
098: * The main method for the TTFReader tool.
099: *
100: * @param args Command-line arguments: [options] fontfile.ttf xmlfile.xml
101: * where options can be:
102: * -fn <fontname>
103: * default is to use the fontname in the .ttf file, but you can override
104: * that name to make sure that the embedded font is used instead of installed
105: * fonts when viewing documents with Acrobat Reader.
106: * -cn <classname>
107: * default is to use the fontname
108: * -ef <path to the truetype fontfile>
109: * will add the possibility to embed the font. When running fop, fop will look
110: * for this file to embed it
111: * -er <path to truetype fontfile relative to org/apache/fop/render/pdf/fonts>
112: * you can also include the fontfile in the fop.jar file when building fop.
113: * You can use both -ef and -er. The file specified in -ef will be searched first,
114: * then the -er file.
115: */
116: public static void main(String[] args) {
117: String embFile = null;
118: String embResource = null;
119: String className = null;
120: String fontName = null;
121: String ttcName = null;
122: boolean isCid = true;
123:
124: Map options = new java.util.HashMap();
125: String[] arguments = parseArguments(options, args);
126:
127: // Enable the simple command line logging when no other logger is
128: // defined.
129: LogFactory logFactory = LogFactory.getFactory();
130: if (System.getProperty("org.apache.commons.logging.Log") == null) {
131: logFactory.setAttribute("org.apache.commons.logging.Log",
132: CommandLineLogger.class.getName());
133: }
134:
135: determineLogLevel(options);
136:
137: TTFReader app = new TTFReader();
138:
139: log.info("TTF Reader for Apache FOP " + Version.getVersion()
140: + "\n");
141:
142: if (options.get("-enc") != null) {
143: String enc = (String) options.get("-enc");
144: if ("ansi".equals(enc)) {
145: isCid = false;
146: }
147: }
148:
149: if (options.get("-ttcname") != null) {
150: ttcName = (String) options.get("-ttcname");
151: }
152:
153: if (options.get("-ef") != null) {
154: embFile = (String) options.get("-ef");
155: }
156:
157: if (options.get("-er") != null) {
158: embResource = (String) options.get("-er");
159: }
160:
161: if (options.get("-fn") != null) {
162: fontName = (String) options.get("-fn");
163: }
164:
165: if (options.get("-cn") != null) {
166: className = (String) options.get("-cn");
167: }
168:
169: if (arguments.length != 2 || options.get("-h") != null
170: || options.get("-help") != null
171: || options.get("--help") != null) {
172: displayUsage();
173: } else {
174: try {
175: log.info("Parsing font...");
176: TTFFile ttf = app.loadTTF(arguments[0], ttcName);
177: if (ttf != null) {
178: org.w3c.dom.Document doc = app.constructFontXML(
179: ttf, fontName, className, embResource,
180: embFile, isCid, ttcName);
181:
182: if (isCid) {
183: log.info("Creating CID encoded metrics...");
184: } else {
185: log.info("Creating WinAnsi encoded metrics...");
186: }
187:
188: if (doc != null) {
189: app.writeFontXML(doc, arguments[1]);
190: }
191:
192: if (ttf.isEmbeddable()) {
193: log
194: .info("This font contains no embedding license restrictions.");
195: } else {
196: log
197: .info("** Note: This font contains license retrictions for\n"
198: + " embedding. This font shouldn't be embedded.");
199: }
200: }
201: log.info("");
202: log.info("XML font metrics file successfully created.");
203: } catch (Exception e) {
204: log.error(
205: "Error while building XML font metrics file.",
206: e);
207: System.exit(-1);
208: }
209: }
210: }
211:
212: /**
213: * Read a TTF file and returns it as an object.
214: *
215: * @param fileName The filename of the TTF file.
216: * @param fontName The name of the font
217: * @return The TTF as an object, null if the font is incompatible.
218: * @throws IOException In case of an I/O problem
219: */
220: public TTFFile loadTTF(String fileName, String fontName)
221: throws IOException {
222: TTFFile ttfFile = new TTFFile();
223: log.info("Reading " + fileName + "...");
224:
225: FontFileReader reader = new FontFileReader(fileName);
226: boolean supported = ttfFile.readFont(reader, fontName);
227: if (!supported) {
228: return null;
229: }
230: log.info("Font Family: " + ttfFile.getFamilyName());
231: if (ttfFile.isCFF()) {
232: throw new UnsupportedOperationException(
233: "OpenType fonts with CFF data are not supported, yet");
234: }
235: return ttfFile;
236: }
237:
238: /**
239: * Generates the font metrics file from the TTF/TTC file.
240: *
241: * @param ttf The PFM file to generate the font metrics from.
242: * @param fontName Name of the font
243: * @param className Class name for the font
244: * @param resource path to the font as embedded resource
245: * @param file path to the font as file
246: * @param isCid True if the font is CID encoded
247: * @param ttcName Name of the TrueType Collection
248: * @return The DOM document representing the font metrics file.
249: */
250: public org.w3c.dom.Document constructFontXML(TTFFile ttf,
251: String fontName, String className, String resource,
252: String file, boolean isCid, String ttcName) {
253: log.info("Creating xml font file...");
254:
255: Document doc;
256: try {
257: DocumentBuilderFactory factory = DocumentBuilderFactory
258: .newInstance();
259: doc = factory.newDocumentBuilder().newDocument();
260: } catch (javax.xml.parsers.ParserConfigurationException e) {
261: log.error("Can't create DOM implementation", e);
262: return null;
263: }
264: Element root = doc.createElement("font-metrics");
265: doc.appendChild(root);
266: root.setAttribute(METRICS_VERSION_ATTR, String
267: .valueOf(METRICS_VERSION));
268: if (isCid) {
269: root.setAttribute("type", "TYPE0");
270: } else {
271: root.setAttribute("type", "TRUETYPE");
272: }
273:
274: Element el = doc.createElement("font-name");
275: root.appendChild(el);
276:
277: // Note that the PostScript name usually is something like
278: // "Perpetua-Bold", but the TrueType spec says that in the ttf file
279: // it should be "Perpetua,Bold".
280:
281: String s = FontUtil.stripWhiteSpace(ttf.getPostScriptName());
282:
283: if (fontName != null) {
284: el.appendChild(doc.createTextNode(FontUtil
285: .stripWhiteSpace(fontName)));
286: } else {
287: el.appendChild(doc.createTextNode(s));
288: }
289:
290: el = doc.createElement("embed");
291: root.appendChild(el);
292: if (file != null && ttf.isEmbeddable()) {
293: el.setAttribute("file", file);
294: }
295: if (resource != null && ttf.isEmbeddable()) {
296: el.setAttribute("class", resource);
297: }
298:
299: el = doc.createElement("cap-height");
300: root.appendChild(el);
301: el.appendChild(doc.createTextNode(String.valueOf(ttf
302: .getCapHeight())));
303:
304: el = doc.createElement("x-height");
305: root.appendChild(el);
306: el.appendChild(doc.createTextNode(String.valueOf(ttf
307: .getXHeight())));
308:
309: el = doc.createElement("ascender");
310: root.appendChild(el);
311: el.appendChild(doc.createTextNode(String.valueOf(ttf
312: .getLowerCaseAscent())));
313:
314: el = doc.createElement("descender");
315: root.appendChild(el);
316: el.appendChild(doc.createTextNode(String.valueOf(ttf
317: .getLowerCaseDescent())));
318:
319: Element bbox = doc.createElement("bbox");
320: root.appendChild(bbox);
321: int[] bb = ttf.getFontBBox();
322: final String[] names = { "left", "bottom", "right", "top" };
323: for (int i = 0; i < names.length; i++) {
324: el = doc.createElement(names[i]);
325: bbox.appendChild(el);
326: el.appendChild(doc.createTextNode(String.valueOf(bb[i])));
327: }
328:
329: el = doc.createElement("flags");
330: root.appendChild(el);
331: el.appendChild(doc.createTextNode(String
332: .valueOf(ttf.getFlags())));
333:
334: el = doc.createElement("stemv");
335: root.appendChild(el);
336: el.appendChild(doc.createTextNode(ttf.getStemV()));
337:
338: el = doc.createElement("italicangle");
339: root.appendChild(el);
340: el.appendChild(doc.createTextNode(ttf.getItalicAngle()));
341:
342: if (ttcName != null) {
343: el = doc.createElement("ttc-name");
344: root.appendChild(el);
345: el.appendChild(doc.createTextNode(ttcName));
346: }
347:
348: el = doc.createElement("subtype");
349: root.appendChild(el);
350:
351: // Fill in extras for CID keyed fonts
352: if (isCid) {
353: el.appendChild(doc.createTextNode("TYPE0"));
354:
355: generateDOM4MultiByteExtras(root, ttf, isCid);
356: } else {
357: // Fill in extras for singlebyte fonts
358: el.appendChild(doc.createTextNode("TRUETYPE"));
359:
360: generateDOM4SingleByteExtras(root, ttf, isCid);
361: }
362:
363: generateDOM4Kerning(root, ttf, isCid);
364:
365: return doc;
366: }
367:
368: private void generateDOM4MultiByteExtras(Element parent,
369: TTFFile ttf, boolean isCid) {
370: Element el;
371: Document doc = parent.getOwnerDocument();
372:
373: Element mel = doc.createElement("multibyte-extras");
374: parent.appendChild(mel);
375:
376: el = doc.createElement("cid-type");
377: mel.appendChild(el);
378: el.appendChild(doc.createTextNode("CIDFontType2"));
379:
380: el = doc.createElement("default-width");
381: mel.appendChild(el);
382: el.appendChild(doc.createTextNode("0"));
383:
384: el = doc.createElement("bfranges");
385: mel.appendChild(el);
386: Iterator iter = ttf.getCMaps().listIterator();
387: while (iter.hasNext()) {
388: TTFCmapEntry ce = (TTFCmapEntry) iter.next();
389: Element el2 = doc.createElement("bf");
390: el.appendChild(el2);
391: el2
392: .setAttribute("us", String.valueOf(ce
393: .getUnicodeStart()));
394: el2.setAttribute("ue", String.valueOf(ce.getUnicodeEnd()));
395: el2.setAttribute("gi", String.valueOf(ce
396: .getGlyphStartIndex()));
397: }
398:
399: el = doc.createElement("cid-widths");
400: el.setAttribute("start-index", "0");
401: mel.appendChild(el);
402:
403: int[] wx = ttf.getWidths();
404: for (int i = 0; i < wx.length; i++) {
405: Element wxel = doc.createElement("wx");
406: wxel.setAttribute("w", String.valueOf(wx[i]));
407: el.appendChild(wxel);
408: }
409: }
410:
411: private void generateDOM4SingleByteExtras(Element parent,
412: TTFFile ttf, boolean isCid) {
413: Element el;
414: Document doc = parent.getOwnerDocument();
415:
416: Element sel = doc.createElement("singlebyte-extras");
417: parent.appendChild(sel);
418:
419: el = doc.createElement("encoding");
420: sel.appendChild(el);
421: el.appendChild(doc.createTextNode(ttf.getCharSetName()));
422:
423: el = doc.createElement("first-char");
424: sel.appendChild(el);
425: el.appendChild(doc.createTextNode(String.valueOf(ttf
426: .getFirstChar())));
427:
428: el = doc.createElement("last-char");
429: sel.appendChild(el);
430: el.appendChild(doc.createTextNode(String.valueOf(ttf
431: .getLastChar())));
432:
433: Element widths = doc.createElement("widths");
434: sel.appendChild(widths);
435:
436: for (short i = ttf.getFirstChar(); i <= ttf.getLastChar(); i++) {
437: el = doc.createElement("char");
438: widths.appendChild(el);
439: el.setAttribute("idx", String.valueOf(i));
440: el.setAttribute("wdt", String.valueOf(ttf.getCharWidth(i)));
441: }
442: }
443:
444: private void generateDOM4Kerning(Element parent, TTFFile ttf,
445: boolean isCid) {
446: Element el;
447: Document doc = parent.getOwnerDocument();
448:
449: // Get kerning
450: Iterator iter;
451: if (isCid) {
452: iter = ttf.getKerning().keySet().iterator();
453: } else {
454: iter = ttf.getAnsiKerning().keySet().iterator();
455: }
456:
457: while (iter.hasNext()) {
458: Integer kpx1 = (Integer) iter.next();
459:
460: el = doc.createElement("kerning");
461: el.setAttribute("kpx1", kpx1.toString());
462: parent.appendChild(el);
463: Element el2 = null;
464:
465: Map h2;
466: if (isCid) {
467: h2 = (Map) ttf.getKerning().get(kpx1);
468: } else {
469: h2 = (Map) ttf.getAnsiKerning().get(kpx1);
470: }
471:
472: Iterator iter2 = h2.keySet().iterator();
473: while (iter2.hasNext()) {
474: Integer kpx2 = (Integer) iter2.next();
475: if (isCid || kpx2.intValue() < 256) {
476: el2 = doc.createElement("pair");
477: el2.setAttribute("kpx2", kpx2.toString());
478: Integer val = (Integer) h2.get(kpx2);
479: el2.setAttribute("kern", val.toString());
480: el.appendChild(el2);
481: }
482: }
483: }
484: }
485:
486: /**
487: * Bugzilla 40739, check that attr has a metrics-version attribute
488: * compatible with ours.
489: * @param attr attributes read from the root element of a metrics XML file
490: * @throws SAXException if incompatible
491: */
492: public static void checkMetricsVersion(Attributes attr)
493: throws SAXException {
494: String err = null;
495: final String str = attr.getValue(METRICS_VERSION_ATTR);
496: if (str == null) {
497: err = "Missing " + METRICS_VERSION_ATTR + " attribute";
498: } else {
499: int version = 0;
500: try {
501: version = Integer.parseInt(str);
502: if (version < METRICS_VERSION) {
503: err = "Incompatible " + METRICS_VERSION_ATTR
504: + " value (" + version + ", should be "
505: + METRICS_VERSION + ")";
506: }
507: } catch (NumberFormatException e) {
508: err = "Invalid " + METRICS_VERSION_ATTR
509: + " attribute value (" + str + ")";
510: }
511: }
512:
513: if (err != null) {
514: throw new SAXException(
515: err
516: + " - please regenerate the font metrics file with "
517: + "a more recent version of FOP.");
518: }
519: }
520:
521: }
|