Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

TTFReader.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fonts.apps;
  19. import java.io.FileInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.util.Iterator;
  23. import java.util.Map;
  24. import java.util.Set;
  25. import javax.xml.parsers.DocumentBuilderFactory;
  26. import org.w3c.dom.Document;
  27. import org.w3c.dom.Element;
  28. import org.xml.sax.Attributes;
  29. import org.xml.sax.SAXException;
  30. import org.apache.commons.logging.LogFactory;
  31. import org.apache.fop.Version;
  32. import org.apache.fop.fonts.FontUtil;
  33. import org.apache.fop.fonts.truetype.FontFileReader;
  34. import org.apache.fop.fonts.truetype.TTFCmapEntry;
  35. import org.apache.fop.fonts.truetype.TTFFile;
  36. import org.apache.fop.util.CommandLineLogger;
  37. // CSOFF: InnerAssignmentCheck
  38. // CSOFF: LineLengthCheck
  39. /**
  40. * A tool which reads TTF files and generates
  41. * XML font metrics file for use in FOP.
  42. */
  43. public class TTFReader extends AbstractFontReader {
  44. /** Used to detect incompatible versions of the generated XML files */
  45. public static final String METRICS_VERSION_ATTR = "metrics-version";
  46. /** Current version number for the metrics file */
  47. public static final int METRICS_VERSION = 2;
  48. /**
  49. * Main constructor.
  50. */
  51. public TTFReader() {
  52. super();
  53. }
  54. private static void displayUsage() {
  55. System.out.println(
  56. "java " + TTFReader.class.getName() + " [options] fontfile.ttf xmlfile.xml");
  57. System.out.println();
  58. System.out.println("where options can be:");
  59. System.out.println("-t Trace mode");
  60. System.out.println("-d Debug mode");
  61. System.out.println("-q Quiet mode");
  62. System.out.println("-enc ansi");
  63. System.out.println(" With this option you create a WinAnsi encoded font.");
  64. System.out.println(" The default is to create a CID keyed font.");
  65. System.out.println(" If you're not going to use characters outside the");
  66. System.out.println(" pdfencoding range (almost the same as iso-8889-1)");
  67. System.out.println(" you can add this option.");
  68. System.out.println("-ttcname <fontname>");
  69. System.out.println(" If you're reading data from a TrueType Collection");
  70. System.out.println(" (.ttc file) you must specify which font from the");
  71. System.out.println(" collection you will read metrics from. If you read");
  72. System.out.println(" from a .ttc file without this option, the fontnames");
  73. System.out.println(" will be listed for you.");
  74. System.out.println(" -fn <fontname>");
  75. System.out.println(" default is to use the fontname in the .ttf file, but");
  76. System.out.println(" you can override that name to make sure that the");
  77. System.out.println(" embedded font is used (if you're embedding fonts)");
  78. System.out.println(" instead of installed fonts when viewing documents ");
  79. System.out.println(" with Acrobat Reader.");
  80. }
  81. /**
  82. * The main method for the TTFReader tool.
  83. *
  84. * @param args Command-line arguments: [options] fontfile.ttf xmlfile.xml
  85. * where options can be:
  86. * -fn <fontname>
  87. * default is to use the fontname in the .ttf file, but you can override
  88. * that name to make sure that the embedded font is used instead of installed
  89. * fonts when viewing documents with Acrobat Reader.
  90. * -cn <classname>
  91. * default is to use the fontname
  92. * -ef <path to the truetype fontfile>
  93. * will add the possibility to embed the font. When running fop, fop will look
  94. * for this file to embed it
  95. * -er <path to truetype fontfile relative to org/apache/fop/render/pdf/fonts>
  96. * you can also include the fontfile in the fop.jar file when building fop.
  97. * You can use both -ef and -er. The file specified in -ef will be searched first,
  98. * then the -er file.
  99. * -nocs
  100. * if complex script features are disabled
  101. */
  102. public static void main(String[] args) {
  103. String embFile = null;
  104. String embResource = null;
  105. String className = null;
  106. String fontName = null;
  107. String ttcName = null;
  108. boolean isCid = true;
  109. Map options = new java.util.HashMap();
  110. String[] arguments = parseArguments(options, args);
  111. // Enable the simple command line logging when no other logger is
  112. // defined.
  113. LogFactory logFactory = LogFactory.getFactory();
  114. if (System.getProperty("org.apache.commons.logging.Log") == null) {
  115. logFactory.setAttribute("org.apache.commons.logging.Log",
  116. CommandLineLogger.class.getName());
  117. }
  118. determineLogLevel(options);
  119. TTFReader app = new TTFReader();
  120. log.info("TTF Reader for Apache FOP " + Version.getVersion() + "\n");
  121. if (options.get("-enc") != null) {
  122. String enc = (String)options.get("-enc");
  123. if ("ansi".equals(enc)) {
  124. isCid = false;
  125. }
  126. }
  127. if (options.get("-ttcname") != null) {
  128. ttcName = (String)options.get("-ttcname");
  129. }
  130. if (options.get("-ef") != null) {
  131. embFile = (String)options.get("-ef");
  132. }
  133. if (options.get("-er") != null) {
  134. embResource = (String)options.get("-er");
  135. }
  136. if (options.get("-fn") != null) {
  137. fontName = (String)options.get("-fn");
  138. }
  139. if (options.get("-cn") != null) {
  140. className = (String)options.get("-cn");
  141. }
  142. boolean useKerning = true;
  143. boolean useAdvanced = true;
  144. if (options.get("-nocs") != null) {
  145. useAdvanced = false;
  146. }
  147. if (arguments.length != 2 || options.get("-h") != null
  148. || options.get("-help") != null || options.get("--help") != null) {
  149. displayUsage();
  150. } else {
  151. try {
  152. log.info("Parsing font...");
  153. TTFFile ttf = app.loadTTF(arguments[0], ttcName, useKerning, useAdvanced);
  154. if (ttf != null) {
  155. org.w3c.dom.Document doc = app.constructFontXML(ttf,
  156. fontName, className, embResource, embFile, isCid,
  157. ttcName);
  158. if (isCid) {
  159. log.info("Creating CID encoded metrics...");
  160. } else {
  161. log.info("Creating WinAnsi encoded metrics...");
  162. }
  163. if (doc != null) {
  164. app.writeFontXML(doc, arguments[1]);
  165. }
  166. if (ttf.isEmbeddable()) {
  167. log.info("This font contains no embedding license restrictions.");
  168. } else {
  169. log.info("** Note: This font contains license retrictions for\n"
  170. + " embedding. This font shouldn't be embedded.");
  171. }
  172. }
  173. log.info("");
  174. log.info("XML font metrics file successfully created.");
  175. } catch (Exception e) {
  176. log.error("Error while building XML font metrics file.", e);
  177. System.exit(-1);
  178. }
  179. }
  180. }
  181. /**
  182. * Read a TTF file and returns it as an object.
  183. *
  184. * @param fileName The filename of the TTF file.
  185. * @param fontName The name of the font
  186. * @param useKerning true if should load kerning data
  187. * @param useAdvanced true if should load advanced typographic table data
  188. * @return The TTF as an object, null if the font is incompatible.
  189. * @throws IOException In case of an I/O problem
  190. */
  191. public TTFFile loadTTF(String fileName, String fontName, boolean useKerning, boolean useAdvanced) throws IOException {
  192. TTFFile ttfFile = new TTFFile(useKerning, useAdvanced);
  193. log.info("Reading " + fileName + "...");
  194. InputStream stream = new FileInputStream(fileName);
  195. try {
  196. FontFileReader reader = new FontFileReader(stream);
  197. boolean supported = ttfFile.readFont(reader, fontName);
  198. if (!supported) {
  199. return null;
  200. }
  201. } finally {
  202. stream.close();
  203. }
  204. log.info("Font Family: " + ttfFile.getFamilyNames());
  205. if (ttfFile.isCFF()) {
  206. throw new UnsupportedOperationException(
  207. "OpenType fonts with CFF data are not supported, yet");
  208. }
  209. return ttfFile;
  210. }
  211. /**
  212. * Generates the font metrics file from the TTF/TTC file.
  213. *
  214. * @param ttf The PFM file to generate the font metrics from.
  215. * @param fontName Name of the font
  216. * @param className Class name for the font
  217. * @param resource path to the font as embedded resource
  218. * @param file path to the font as file
  219. * @param isCid True if the font is CID encoded
  220. * @param ttcName Name of the TrueType Collection
  221. * @return The DOM document representing the font metrics file.
  222. */
  223. public org.w3c.dom.Document constructFontXML(TTFFile ttf,
  224. String fontName, String className, String resource, String file,
  225. boolean isCid, String ttcName) {
  226. log.info("Creating xml font file...");
  227. Document doc;
  228. try {
  229. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  230. doc = factory.newDocumentBuilder().newDocument();
  231. } catch (javax.xml.parsers.ParserConfigurationException e) {
  232. log.error("Can't create DOM implementation", e);
  233. return null;
  234. }
  235. Element root = doc.createElement("font-metrics");
  236. doc.appendChild(root);
  237. root.setAttribute(METRICS_VERSION_ATTR, String.valueOf(METRICS_VERSION));
  238. if (isCid) {
  239. root.setAttribute("type", "TYPE0");
  240. } else {
  241. root.setAttribute("type", "TRUETYPE");
  242. }
  243. Element el = doc.createElement("font-name");
  244. root.appendChild(el);
  245. // Note that the PostScript name usually is something like
  246. // "Perpetua-Bold", but the TrueType spec says that in the ttf file
  247. // it should be "Perpetua,Bold".
  248. String s = FontUtil.stripWhiteSpace(ttf.getPostScriptName());
  249. if (fontName != null) {
  250. el.appendChild(doc.createTextNode(FontUtil.stripWhiteSpace(fontName)));
  251. } else {
  252. el.appendChild(doc.createTextNode(s));
  253. }
  254. if (ttf.getFullName() != null) {
  255. el = doc.createElement("full-name");
  256. root.appendChild(el);
  257. el.appendChild(doc.createTextNode(ttf.getFullName()));
  258. }
  259. Set familyNames = ttf.getFamilyNames();
  260. if (familyNames.size() > 0) {
  261. String familyName = (String)familyNames.iterator().next();
  262. el = doc.createElement("family-name");
  263. root.appendChild(el);
  264. el.appendChild(doc.createTextNode(familyName));
  265. }
  266. el = doc.createElement("embed");
  267. root.appendChild(el);
  268. if (file != null && ttf.isEmbeddable()) {
  269. el.setAttribute("file", file);
  270. }
  271. if (resource != null && ttf.isEmbeddable()) {
  272. el.setAttribute("class", resource);
  273. }
  274. el = doc.createElement("cap-height");
  275. root.appendChild(el);
  276. el.appendChild(doc.createTextNode(String.valueOf(ttf.getCapHeight())));
  277. el = doc.createElement("x-height");
  278. root.appendChild(el);
  279. el.appendChild(doc.createTextNode(String.valueOf(ttf.getXHeight())));
  280. el = doc.createElement("ascender");
  281. root.appendChild(el);
  282. el.appendChild(doc.createTextNode(String.valueOf(ttf.getLowerCaseAscent())));
  283. el = doc.createElement("descender");
  284. root.appendChild(el);
  285. el.appendChild(doc.createTextNode(String.valueOf(ttf.getLowerCaseDescent())));
  286. Element bbox = doc.createElement("bbox");
  287. root.appendChild(bbox);
  288. int[] bb = ttf.getFontBBox();
  289. final String[] names = {"left", "bottom", "right", "top"};
  290. for (int i = 0; i < names.length; i++) {
  291. el = doc.createElement(names[i]);
  292. bbox.appendChild(el);
  293. el.appendChild(doc.createTextNode(String.valueOf(bb[i])));
  294. }
  295. el = doc.createElement("flags");
  296. root.appendChild(el);
  297. el.appendChild(doc.createTextNode(String.valueOf(ttf.getFlags())));
  298. el = doc.createElement("stemv");
  299. root.appendChild(el);
  300. el.appendChild(doc.createTextNode(ttf.getStemV()));
  301. el = doc.createElement("italicangle");
  302. root.appendChild(el);
  303. el.appendChild(doc.createTextNode(ttf.getItalicAngle()));
  304. if (ttcName != null) {
  305. el = doc.createElement("ttc-name");
  306. root.appendChild(el);
  307. el.appendChild(doc.createTextNode(ttcName));
  308. }
  309. el = doc.createElement("subtype");
  310. root.appendChild(el);
  311. // Fill in extras for CID keyed fonts
  312. if (isCid) {
  313. el.appendChild(doc.createTextNode("TYPE0"));
  314. generateDOM4MultiByteExtras(root, ttf, isCid);
  315. } else {
  316. // Fill in extras for singlebyte fonts
  317. el.appendChild(doc.createTextNode("TRUETYPE"));
  318. generateDOM4SingleByteExtras(root, ttf, isCid);
  319. }
  320. generateDOM4Kerning(root, ttf, isCid);
  321. return doc;
  322. }
  323. private void generateDOM4MultiByteExtras(Element parent, TTFFile ttf, boolean isCid) {
  324. Element el;
  325. Document doc = parent.getOwnerDocument();
  326. Element mel = doc.createElement("multibyte-extras");
  327. parent.appendChild(mel);
  328. el = doc.createElement("cid-type");
  329. mel.appendChild(el);
  330. el.appendChild(doc.createTextNode("CIDFontType2"));
  331. el = doc.createElement("default-width");
  332. mel.appendChild(el);
  333. el.appendChild(doc.createTextNode("0"));
  334. el = doc.createElement("bfranges");
  335. mel.appendChild(el);
  336. Iterator iter = ttf.getCMaps().listIterator();
  337. while (iter.hasNext()) {
  338. TTFCmapEntry ce = (TTFCmapEntry)iter.next();
  339. Element el2 = doc.createElement("bf");
  340. el.appendChild(el2);
  341. el2.setAttribute("us", String.valueOf(ce.getUnicodeStart()));
  342. el2.setAttribute("ue", String.valueOf(ce.getUnicodeEnd()));
  343. el2.setAttribute("gi", String.valueOf(ce.getGlyphStartIndex()));
  344. }
  345. el = doc.createElement("cid-widths");
  346. el.setAttribute("start-index", "0");
  347. mel.appendChild(el);
  348. int[] wx = ttf.getWidths();
  349. for (int i = 0; i < wx.length; i++) {
  350. Element wxel = doc.createElement("wx");
  351. wxel.setAttribute("w", String.valueOf(wx[i]));
  352. el.appendChild(wxel);
  353. }
  354. }
  355. private void generateDOM4SingleByteExtras(Element parent, TTFFile ttf, boolean isCid) {
  356. Element el;
  357. Document doc = parent.getOwnerDocument();
  358. Element sel = doc.createElement("singlebyte-extras");
  359. parent.appendChild(sel);
  360. el = doc.createElement("encoding");
  361. sel.appendChild(el);
  362. el.appendChild(doc.createTextNode(ttf.getCharSetName()));
  363. el = doc.createElement("first-char");
  364. sel.appendChild(el);
  365. el.appendChild(doc.createTextNode(String.valueOf(ttf.getFirstChar())));
  366. el = doc.createElement("last-char");
  367. sel.appendChild(el);
  368. el.appendChild(doc.createTextNode(String.valueOf(ttf.getLastChar())));
  369. Element widths = doc.createElement("widths");
  370. sel.appendChild(widths);
  371. for (short i = ttf.getFirstChar(); i <= ttf.getLastChar(); i++) {
  372. el = doc.createElement("char");
  373. widths.appendChild(el);
  374. el.setAttribute("idx", String.valueOf(i));
  375. el.setAttribute("wdt", String.valueOf(ttf.getCharWidth(i)));
  376. }
  377. }
  378. private void generateDOM4Kerning(Element parent, TTFFile ttf, boolean isCid) {
  379. Element el;
  380. Document doc = parent.getOwnerDocument();
  381. // Get kerning
  382. Iterator iter;
  383. if (isCid) {
  384. iter = ttf.getKerning().keySet().iterator();
  385. } else {
  386. iter = ttf.getAnsiKerning().keySet().iterator();
  387. }
  388. while (iter.hasNext()) {
  389. Integer kpx1 = (Integer)iter.next();
  390. el = doc.createElement("kerning");
  391. el.setAttribute("kpx1", kpx1.toString());
  392. parent.appendChild(el);
  393. Element el2 = null;
  394. Map h2;
  395. if (isCid) {
  396. h2 = ttf.getKerning().get(kpx1);
  397. } else {
  398. h2 = ttf.getAnsiKerning().get(kpx1);
  399. }
  400. Iterator iter2 = h2.keySet().iterator();
  401. while (iter2.hasNext()) {
  402. Integer kpx2 = (Integer)iter2.next();
  403. if (isCid || kpx2.intValue() < 256) {
  404. el2 = doc.createElement("pair");
  405. el2.setAttribute("kpx2", kpx2.toString());
  406. Integer val = (Integer)h2.get(kpx2);
  407. el2.setAttribute("kern", val.toString());
  408. el.appendChild(el2);
  409. }
  410. }
  411. }
  412. }
  413. /**
  414. * Bugzilla 40739, check that attr has a metrics-version attribute
  415. * compatible with ours.
  416. * @param attr attributes read from the root element of a metrics XML file
  417. * @throws SAXException if incompatible
  418. */
  419. public static void checkMetricsVersion(Attributes attr) throws SAXException {
  420. String err = null;
  421. final String str = attr.getValue(METRICS_VERSION_ATTR);
  422. if (str == null) {
  423. err = "Missing " + METRICS_VERSION_ATTR + " attribute";
  424. } else {
  425. int version = 0;
  426. try {
  427. version = Integer.parseInt(str);
  428. if (version < METRICS_VERSION) {
  429. err = "Incompatible " + METRICS_VERSION_ATTR
  430. + " value (" + version + ", should be " + METRICS_VERSION
  431. + ")";
  432. }
  433. } catch (NumberFormatException e) {
  434. err = "Invalid " + METRICS_VERSION_ATTR
  435. + " attribute value (" + str + ")";
  436. }
  437. }
  438. if (err != null) {
  439. throw new SAXException(
  440. err
  441. + " - please regenerate the font metrics file with "
  442. + "a more recent version of FOP."
  443. );
  444. }
  445. }
  446. }