From cb742f91f7809f8a15157ceb2ed79f0473946c56 Mon Sep 17 00:00:00 2001
From: Vincent Hennebert
Date: Tue, 30 Jul 2013 20:10:35 +0000
Subject: Removed obsolete CommandLineLogger. The Jdk14Logger is guaranteed to
be there by default now that the minimal Java requirement is 1.5. Also,
CommandLineLogger messes up the output when rendering to stdout.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1508599 13f79535-47bb-0310-9956-ffa450edef68
---
.../org/apache/fop/cli/CommandLineOptions.java | 37 +--
.../apache/fop/fonts/apps/AbstractFontReader.java | 6 -
src/java/org/apache/fop/fonts/apps/PFMReader.java | 8 -
src/java/org/apache/fop/fonts/apps/TTFReader.java | 8 -
.../org/apache/fop/util/CommandLineLogger.java | 263 ---------------------
5 files changed, 2 insertions(+), 320 deletions(-)
delete mode 100644 src/java/org/apache/fop/util/CommandLineLogger.java
(limited to 'src/java/org/apache/fop')
diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java
index 080fe7930..627b95a47 100644
--- a/src/java/org/apache/fop/cli/CommandLineOptions.java
+++ b/src/java/org/apache/fop/cli/CommandLineOptions.java
@@ -58,7 +58,6 @@ import org.apache.fop.render.pdf.PDFEncryptionOption;
import org.apache.fop.render.print.PagesMode;
import org.apache.fop.render.print.PrintRenderer;
import org.apache.fop.render.xml.XMLRenderer;
-import org.apache.fop.util.CommandLineLogger;
/**
* Options parses the commandline arguments
@@ -146,15 +145,6 @@ public class CommandLineOptions {
*/
public CommandLineOptions() {
LogFactory logFactory = LogFactory.getFactory();
-
- // Enable the simple command line logging when no other logger is
- // defined.
- if (System.getProperty("org.apache.commons.logging.Log") == null) {
- logFactory.setAttribute("org.apache.commons.logging.Log",
- CommandLineLogger.class.getName());
- setLogLevel("info");
- }
-
log = LogFactory.getLog("FOP");
}
@@ -292,7 +282,7 @@ public class CommandLineOptions {
} else if (args[i].equals("-s")) {
suppressLowLevelAreas = Boolean.TRUE;
} else if (args[i].equals("-d")) {
- setLogOption("debug", "debug");
+ // nop. Left there for backwards compatibility
} else if (args[i].equals("-r")) {
strictValidation = false;
} else if (args[i].equals("-conserve")) {
@@ -304,7 +294,7 @@ public class CommandLineOptions {
} else if (args[i].equals("-dpi")) {
i = i + parseResolution(args, i);
} else if (args[i].equals("-q") || args[i].equals("--quiet")) {
- setLogOption("quiet", "error");
+ // nop. Left there for backwards compatibility
} else if (args[i].equals("-fo")) {
i = i + parseFOInputOption(args, i);
} else if (args[i].equals("-xsl")) {
@@ -904,27 +894,6 @@ public class CommandLineOptions {
}
}
- private void setLogOption(String option, String level) {
- if (log instanceof CommandLineLogger
- || System.getProperty("org.apache.commons.logging.Log") == null) {
- setLogLevel(level);
- } else if (log != null) {
- log.warn("The option " + option + " can only be used");
- log.warn("with FOP's command line logger,");
- log.warn("which is the default on the command line.");
- log.warn("Configure other loggers using Java system properties.");
- }
- }
-
- private void setLogLevel(String level) {
- // Set the level for future loggers.
- LogFactory.getFactory().setAttribute("level", level);
- if (log instanceof CommandLineLogger) {
- // Set the level for the logger created already.
- ((CommandLineLogger) log).setLogLevel(level);
- }
- }
-
private void setInputFormat(int format) throws FOPException {
if (inputmode == NOT_SET || inputmode == format) {
inputmode = format;
@@ -1208,9 +1177,7 @@ public class CommandLineOptions {
+ "[-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl|-ps|-txt|-at [mime]|-print] \n"
+ " [OPTIONS] \n"
+ " -version print FOP version and exit\n"
- + " -d debug mode \n"
+ " -x dump configuration settings \n"
- + " -q quiet mode \n"
+ " -c cfg.xml use additional configuration file cfg.xml\n"
+ " -l lang the language to use for user information \n"
+ " -nocs disable complex script features\n"
diff --git a/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java b/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java
index 39f7e14a4..3da33ec3f 100644
--- a/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java
+++ b/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java
@@ -32,8 +32,6 @@ import javax.xml.transform.TransformerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.fop.util.CommandLineLogger;
-
/**
* Abstract base class for the PFM and TTF Reader command-line applications.
*/
@@ -90,10 +88,6 @@ public abstract class AbstractFontReader {
protected static void setLogLevel(String level) {
// Set the evel for future loggers.
LogFactory.getFactory().setAttribute("level", level);
- if (log instanceof CommandLineLogger) {
- // Set the level for the logger creates already.
- ((CommandLineLogger) log).setLogLevel(level);
- }
}
/**
diff --git a/src/java/org/apache/fop/fonts/apps/PFMReader.java b/src/java/org/apache/fop/fonts/apps/PFMReader.java
index e5e8ca524..8ee6ff9b6 100644
--- a/src/java/org/apache/fop/fonts/apps/PFMReader.java
+++ b/src/java/org/apache/fop/fonts/apps/PFMReader.java
@@ -33,7 +33,6 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.Version;
import org.apache.fop.fonts.type1.PFMFile;
-import org.apache.fop.util.CommandLineLogger;
/**
* A tool which reads PFM files from Adobe Type 1 fonts and creates
@@ -92,14 +91,7 @@ public class PFMReader extends AbstractFontReader {
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);
PFMReader app = new PFMReader();
diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java
index 900417350..db858e285 100644
--- a/src/java/org/apache/fop/fonts/apps/TTFReader.java
+++ b/src/java/org/apache/fop/fonts/apps/TTFReader.java
@@ -39,7 +39,6 @@ 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.TTFFile;
-import org.apache.fop.util.CommandLineLogger;
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
@@ -123,14 +122,7 @@ public class TTFReader extends AbstractFontReader {
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();
diff --git a/src/java/org/apache/fop/util/CommandLineLogger.java b/src/java/org/apache/fop/util/CommandLineLogger.java
deleted file mode 100644
index a9e59158f..000000000
--- a/src/java/org/apache/fop/util/CommandLineLogger.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * 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.util;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * This is a commons-logging logger for command line use.
- */
-public class CommandLineLogger implements Log {
- /** "Trace" level logging. */
- public static final int LOG_LEVEL_TRACE = 1;
- /** "Debug" level logging. */
- public static final int LOG_LEVEL_DEBUG = 2;
- /** "Info" level logging. */
- public static final int LOG_LEVEL_INFO = 3;
- /** "Warn" level logging. */
- public static final int LOG_LEVEL_WARN = 4;
- /** "Error" level logging. */
- public static final int LOG_LEVEL_ERROR = 5;
- /** "Fatal" level logging. */
- public static final int LOG_LEVEL_FATAL = 6;
-
- private int logLevel;
- private String logName;
-
- /**
- * Construct the logger with a default log level taken from the LogFactory
- * attribute "level".
- * @param logName the logger name.
- */
- public CommandLineLogger(String logName) {
- this.logName = logName;
- setLogLevel((String) LogFactory.getFactory().getAttribute("level"));
- }
-
- /**
- * Set a log level for the logger.
- * @param level the log level
- */
- public void setLogLevel(String level) {
- if ("fatal".equals(level)) {
- logLevel = LOG_LEVEL_FATAL;
- } else if ("error".equals(level)) {
- logLevel = LOG_LEVEL_ERROR;
- } else if ("warn".equals(level)) {
- logLevel = LOG_LEVEL_WARN;
- } else if ("info".equals(level)) {
- logLevel = LOG_LEVEL_INFO;
- } else if ("debug".equals(level)) {
- logLevel = LOG_LEVEL_DEBUG;
- } else if ("trace".equals(level)) {
- logLevel = LOG_LEVEL_TRACE;
- } else {
- logLevel = LOG_LEVEL_INFO;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final boolean isTraceEnabled() {
- return logLevel <= LOG_LEVEL_TRACE;
- }
-
- /**
- * {@inheritDoc}
- */
- public final boolean isDebugEnabled() {
- return logLevel <= LOG_LEVEL_DEBUG;
- }
-
- /**
- * {@inheritDoc}
- */
- public final boolean isInfoEnabled() {
- return logLevel <= LOG_LEVEL_INFO;
- }
-
- /**
- * {@inheritDoc}
- */
- public final boolean isWarnEnabled() {
- return logLevel <= LOG_LEVEL_WARN;
- }
-
- /**
- * {@inheritDoc}
- */
- public final boolean isErrorEnabled() {
- return logLevel <= LOG_LEVEL_ERROR;
- }
-
- /**
- * {@inheritDoc}
- */
- public final boolean isFatalEnabled() {
- return logLevel <= LOG_LEVEL_FATAL;
- }
-
- /**
- * {@inheritDoc}
- */
- public final void trace(Object message) {
- if (isTraceEnabled()) {
- log(LOG_LEVEL_TRACE, message, null);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void trace(Object message, Throwable t) {
- if (isTraceEnabled()) {
- log(LOG_LEVEL_TRACE, message, t);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void debug(Object message) {
- if (isDebugEnabled()) {
- log(LOG_LEVEL_DEBUG, message, null);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void debug(Object message, Throwable t) {
- if (isDebugEnabled()) {
- log(LOG_LEVEL_DEBUG, message, t);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void info(Object message) {
- if (isInfoEnabled()) {
- log(LOG_LEVEL_INFO, message, null);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void info(Object message, Throwable t) {
- if (isInfoEnabled()) {
- log(LOG_LEVEL_INFO, message, t);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void warn(Object message) {
- if (isWarnEnabled()) {
- log(LOG_LEVEL_WARN, message, null);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void warn(Object message, Throwable t) {
- if (isWarnEnabled()) {
- log(LOG_LEVEL_WARN, message, t);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void error(Object message) {
- if (isErrorEnabled()) {
- log(LOG_LEVEL_ERROR, message, null);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void error(Object message, Throwable t) {
- if (isErrorEnabled()) {
- log(LOG_LEVEL_ERROR, message, t);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void fatal(Object message) {
- if (isFatalEnabled()) {
- log(LOG_LEVEL_FATAL, message, null);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public final void fatal(Object message, Throwable t) {
- if (isFatalEnabled()) {
- log(LOG_LEVEL_FATAL, message, t);
- }
- }
-
- /**
- * Do the actual logging.
- * This method assembles the message and prints it to
- * and then calls write()
to cause it to be written.
- *
- * @param type One of the LOG_LEVEL_XXX constants defining the log level
- * @param message The message itself (typically a String)
- * @param t The exception whose stack trace should be logged
- */
- protected void log(int type, Object message, Throwable t) {
- StringBuffer buf = new StringBuffer();
- // Append the message
- buf.append(String.valueOf(message));
- if (t != null) {
- buf.append("\n");
- // Append a stack trace or just the stack trace message.
- if (!isDebugEnabled()) {
- buf.append(t.toString());
- buf.append("\n");
- } else {
- java.io.StringWriter sw = new java.io.StringWriter(1024);
- java.io.PrintWriter pw = new java.io.PrintWriter(sw);
- t.printStackTrace(pw);
- pw.close();
- buf.append(sw.toString());
- }
- }
-
- // Print to the appropriate destination
- if (type >= LOG_LEVEL_WARN) {
- System.err.println(buf);
- } else {
- System.out.println(buf);
- }
-
- }
-}
--
cgit v1.2.3
From 21682bdca71d422e03b4452fa841b6b542d0c22f Mon Sep 17 00:00:00 2001
From: Luis Bernardo
Date: Wed, 14 Aug 2013 22:35:37 +0000
Subject: FOP-2252: OpenType CFF support for FOP; patch submitted by Robert
Meyer
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1514076 13f79535-47bb-0310-9956-ffa450edef68
---
findbugs-exclude.xml | 219 ++-
lib/fontbox-1.8.0-SNAPSHOT.jar | Bin 0 -> 207498 bytes
lib/xmlgraphics-commons-svn-trunk.jar | Bin 629283 -> 629507 bytes
.../fonts/OTFAdvancedTypographicTableReader.java | 85 +-
src/java/org/apache/fop/fonts/FontLoader.java | 4 +-
src/java/org/apache/fop/fonts/MultiByteFont.java | 69 +-
src/java/org/apache/fop/fonts/SingleByteFont.java | 2 +-
src/java/org/apache/fop/fonts/apps/TTFReader.java | 4 +-
.../fop/fonts/autodetect/FontInfoFinder.java | 4 +-
.../org/apache/fop/fonts/truetype/GlyfTable.java | 4 +-
.../apache/fop/fonts/truetype/OFDirTabEntry.java | 119 ++
.../apache/fop/fonts/truetype/OFFontLoader.java | 261 +++
.../org/apache/fop/fonts/truetype/OFMtxEntry.java | 195 ++
.../org/apache/fop/fonts/truetype/OFTableName.java | 169 ++
.../org/apache/fop/fonts/truetype/OTFFile.java | 109 ++
.../apache/fop/fonts/truetype/OTFSubSetFile.java | 1092 +++++++++++
.../org/apache/fop/fonts/truetype/OpenFont.java | 1937 +++++++++++++++++++
.../apache/fop/fonts/truetype/TTFDirTabEntry.java | 119 --
.../org/apache/fop/fonts/truetype/TTFFile.java | 2011 +-------------------
.../apache/fop/fonts/truetype/TTFFontLoader.java | 252 ---
.../org/apache/fop/fonts/truetype/TTFMtxEntry.java | 195 --
.../apache/fop/fonts/truetype/TTFSubSetFile.java | 106 +-
.../apache/fop/fonts/truetype/TTFTableName.java | 163 --
.../org/apache/fop/pdf/PDFCFFStreamType0C.java | 74 +
src/java/org/apache/fop/pdf/PDFEncryptionJCE.java | 99 +-
src/java/org/apache/fop/pdf/PDFFactory.java | 57 +-
src/java/org/apache/fop/pdf/PDFFontDescriptor.java | 2 +
src/java/org/apache/fop/render/ps/PSFontUtils.java | 183 +-
src/java/org/apache/fop/render/ps/PSPainter.java | 49 +-
.../fop/fonts/cff/CFFDataReaderTestCase.java | 154 ++
.../fop/fonts/truetype/GlyfTableTestCase.java | 7 +-
.../apache/fop/fonts/truetype/OTFFileTestCase.java | 86 +
.../fop/fonts/truetype/OTFSubSetFileTestCase.java | 148 ++
.../apache/fop/fonts/truetype/TTFFileTestCase.java | 14 +-
.../fop/fonts/truetype/TTFFontLoaderTestCase.java | 6 +-
.../fop/fonts/truetype/TTFSubSetFileTestCase.java | 7 +-
.../fop/fonts/truetype/TTFTableNameTestCase.java | 126 +-
test/resources/fonts/otf/AlexBrushRegular.otf | Bin 0 -> 39796 bytes
test/resources/fonts/otf/SourceSansProBold.otf | Bin 0 -> 104072 bytes
39 files changed, 5188 insertions(+), 2943 deletions(-)
create mode 100644 lib/fontbox-1.8.0-SNAPSHOT.jar
create mode 100644 src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java
create mode 100644 src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
create mode 100644 src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java
create mode 100644 src/java/org/apache/fop/fonts/truetype/OFTableName.java
create mode 100644 src/java/org/apache/fop/fonts/truetype/OTFFile.java
create mode 100644 src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java
create mode 100644 src/java/org/apache/fop/fonts/truetype/OpenFont.java
delete mode 100644 src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
delete mode 100644 src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
delete mode 100644 src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java
delete mode 100644 src/java/org/apache/fop/fonts/truetype/TTFTableName.java
create mode 100644 src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java
create mode 100644 test/java/org/apache/fop/fonts/cff/CFFDataReaderTestCase.java
create mode 100644 test/java/org/apache/fop/fonts/truetype/OTFFileTestCase.java
create mode 100644 test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java
create mode 100644 test/resources/fonts/otf/AlexBrushRegular.otf
create mode 100644 test/resources/fonts/otf/SourceSansProBold.otf
(limited to 'src/java/org/apache/fop')
diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml
index 31edec268..2becac909 100644
--- a/findbugs-exclude.xml
+++ b/findbugs-exclude.xml
@@ -1,7 +1,7 @@
-
+
@@ -408,6 +408,11 @@
+
+
+
+
+
@@ -834,7 +839,7 @@
-
+
@@ -989,7 +994,7 @@
-
+
@@ -2143,37 +2148,37 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2623,7 +2628,7 @@
-
+
@@ -2852,7 +2857,7 @@
-
+
@@ -2902,7 +2907,7 @@
-
+
@@ -3082,7 +3087,7 @@
-
+
@@ -3747,7 +3752,7 @@
-
+
@@ -5021,7 +5026,7 @@
-
+
@@ -5226,4 +5231,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/fontbox-1.8.0-SNAPSHOT.jar b/lib/fontbox-1.8.0-SNAPSHOT.jar
new file mode 100644
index 000000000..e20e1ad7b
Binary files /dev/null and b/lib/fontbox-1.8.0-SNAPSHOT.jar differ
diff --git a/lib/xmlgraphics-commons-svn-trunk.jar b/lib/xmlgraphics-commons-svn-trunk.jar
index c7ff1d6a4..22b0f5d52 100644
Binary files a/lib/xmlgraphics-commons-svn-trunk.jar and b/lib/xmlgraphics-commons-svn-trunk.jar differ
diff --git a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
index b5457e4e4..4fa6c3b62 100644
--- a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
+++ b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
@@ -29,9 +29,9 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.fonts.truetype.FontFileReader;
-import org.apache.fop.fonts.truetype.TTFDirTabEntry;
-import org.apache.fop.fonts.truetype.TTFFile;
-import org.apache.fop.fonts.truetype.TTFTableName;
+import org.apache.fop.fonts.truetype.OFDirTabEntry;
+import org.apache.fop.fonts.truetype.OFTableName;
+import org.apache.fop.fonts.truetype.OpenFont;
// CSOFF: AvoidNestedBlocksCheck
// CSOFF: NoWhitespaceAfterCheck
@@ -50,7 +50,7 @@ public final class OTFAdvancedTypographicTableReader {
// logging state
private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class);
// instance state
- private TTFFile ttf; // parent font file reader
+ private OpenFont otf; // parent font file reader
private FontFileReader in; // input reader
private GlyphDefinitionTable gdef; // glyph definition table
private GlyphSubstitutionTable gsub; // glyph substitution table
@@ -68,10 +68,10 @@ public final class OTFAdvancedTypographicTableReader {
* @param ttf parent font file reader (must be non-null)
* @param in font file reader (must be non-null)
*/
- public OTFAdvancedTypographicTableReader(TTFFile ttf, FontFileReader in) {
- assert ttf != null;
+ public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) {
+ assert otf != null;
assert in != null;
- this.ttf = ttf;
+ this.otf = otf;
this.in = in;
}
@@ -127,7 +127,8 @@ public final class OTFAdvancedTypographicTableReader {
return gpos;
}
- private void readLangSysTable(TTFTableName tableTag, long langSysTable, String langSysTag) throws IOException {
+ private void readLangSysTable(OFTableName tableTag, long langSysTable, String langSysTag)
+ throws IOException {
in.seekSet(langSysTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys table: " + langSysTag);
@@ -169,7 +170,7 @@ public final class OTFAdvancedTypographicTableReader {
private static String defaultTag = "dflt";
- private void readScriptTable(TTFTableName tableTag, long scriptTable, String scriptTag) throws IOException {
+ private void readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag) throws IOException {
in.seekSet(scriptTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " script table: " + scriptTag);
@@ -222,7 +223,7 @@ public final class OTFAdvancedTypographicTableReader {
seLanguages = null;
}
- private void readScriptList(TTFTableName tableTag, long scriptList) throws IOException {
+ private void readScriptList(OFTableName tableTag, long scriptList) throws IOException {
in.seekSet(scriptList);
// read script record count
int ns = in.readTTFUShort();
@@ -251,7 +252,7 @@ public final class OTFAdvancedTypographicTableReader {
}
}
- private void readFeatureTable(TTFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
+ private void readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
in.seekSet(featureTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature table: " + featureTag);
@@ -279,7 +280,7 @@ public final class OTFAdvancedTypographicTableReader {
seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul });
}
- private void readFeatureList(TTFTableName tableTag, long featureList) throws IOException {
+ private void readFeatureList(OFTableName tableTag, long featureList) throws IOException {
in.seekSet(featureList);
// read feature record count
int nf = in.readTTFUShort();
@@ -1736,28 +1737,28 @@ public final class OTFAdvancedTypographicTableReader {
// XPlacement
int xp;
if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) {
- xp = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
xp = 0;
}
// YPlacement
int yp;
if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) {
- yp = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
yp = 0;
}
// XAdvance
int xa;
if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) {
- xa = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
xa = 0;
}
// YAdvance
int ya;
if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) {
- ya = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
ya = 0;
}
@@ -2029,23 +2030,23 @@ public final class OTFAdvancedTypographicTableReader {
int af = in.readTTFUShort();
if (af == 1) {
// read x coordinate
- int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read y coordinate
- int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
a = new GlyphPositioningTable.Anchor(x, y);
} else if (af == 2) {
// read x coordinate
- int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read y coordinate
- int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read anchor point index
int ap = in.readTTFUShort();
a = new GlyphPositioningTable.Anchor(x, y, ap);
} else if (af == 3) {
// read x coordinate
- int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read y coordinate
- int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort());
+ int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read x device table offset
int xdo = in.readTTFUShort();
// read y device table offset
@@ -3145,9 +3146,9 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}
- private void readLookupTable(TTFTableName tableTag, int lookupSequence, long lookupTable) throws IOException {
- boolean isGSUB = tableTag.equals(TTFTableName.GSUB);
- boolean isGPOS = tableTag.equals(TTFTableName.GPOS);
+ private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException {
+ boolean isGSUB = tableTag.equals(OFTableName.GSUB);
+ boolean isGPOS = tableTag.equals(OFTableName.GPOS);
in.seekSet(lookupTable);
// read lookup type
int lt = in.readTTFUShort();
@@ -3198,7 +3199,7 @@ public final class OTFAdvancedTypographicTableReader {
}
}
- private void readLookupList(TTFTableName tableTag, long lookupList) throws IOException {
+ private void readLookupList(OFTableName tableTag, long lookupList) throws IOException {
in.seekSet(lookupList);
// read lookup record count
int nl = in.readTTFUShort();
@@ -3233,7 +3234,7 @@ public final class OTFAdvancedTypographicTableReader {
* @param lookupList offset to lookup list from beginning of font file
* @throws IOException In case of a I/O problem
*/
- private void readCommonLayoutTables(TTFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException {
+ private void readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException {
if (scriptList > 0) {
readScriptList(tableTag, scriptList);
}
@@ -3245,7 +3246,7 @@ public final class OTFAdvancedTypographicTableReader {
}
}
- private void readGDEFClassDefTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
@@ -3257,7 +3258,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}
- private void readGDEFAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ private void readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
@@ -3275,7 +3276,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}
- private void readGDEFLigatureCaretTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ private void readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
@@ -3305,7 +3306,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}
- private void readGDEFMarkAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ private void readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
@@ -3317,7 +3318,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}
- private void readGDEFMarkGlyphsTableFormat1(TTFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
+ private void readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// skip over format (already known)
@@ -3351,7 +3352,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}
- private void readGDEFMarkGlyphsTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ private void readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read mark set subtable format
int sf = in.readTTFUShort();
@@ -3367,17 +3368,17 @@ public final class OTFAdvancedTypographicTableReader {
* @throws IOException In case of a I/O problem
*/
private void readGDEF() throws IOException {
- TTFTableName tableTag = TTFTableName.GDEF;
+ OFTableName tableTag = OFTableName.GDEF;
// Initialize temporary state
initATState();
// Read glyph definition (GDEF) table
- TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag);
+ OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag);
if (gdef != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
- ttf.seekTab(in, tableTag, 0);
+ otf.seekTab(in, tableTag, 0);
long version = in.readTTFULong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
@@ -3440,17 +3441,17 @@ public final class OTFAdvancedTypographicTableReader {
* @throws IOException In case of a I/O problem
*/
private void readGSUB() throws IOException {
- TTFTableName tableTag = TTFTableName.GSUB;
+ OFTableName tableTag = OFTableName.GSUB;
// Initialize temporary state
initATState();
// Read glyph substitution (GSUB) table
- TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag);
+ OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag);
if (gpos != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
- ttf.seekTab(in, tableTag, 0);
+ otf.seekTab(in, tableTag, 0);
int version = in.readTTFLong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
@@ -3477,17 +3478,17 @@ public final class OTFAdvancedTypographicTableReader {
* @throws IOException In case of a I/O problem
*/
private void readGPOS() throws IOException {
- TTFTableName tableTag = TTFTableName.GPOS;
+ OFTableName tableTag = OFTableName.GPOS;
// Initialize temporary state
initATState();
// Read glyph positioning (GPOS) table
- TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag);
+ OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag);
if (gpos != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
- ttf.seekTab(in, tableTag, 0);
+ otf.seekTab(in, tableTag, 0);
int version = in.readTTFLong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java
index f7ee24cbc..09e38260e 100644
--- a/src/java/org/apache/fop/fonts/FontLoader.java
+++ b/src/java/org/apache/fop/fonts/FontLoader.java
@@ -26,7 +26,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.apps.io.InternalResourceResolver;
-import org.apache.fop.fonts.truetype.TTFFontLoader;
+import org.apache.fop.fonts.truetype.OFFontLoader;
import org.apache.fop.fonts.type1.Type1FontLoader;
/**
@@ -105,7 +105,7 @@ public abstract class FontLoader {
}
loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resourceResolver);
} else {
- loader = new TTFFontLoader(fontFileURI, subFontName, embedded, embeddingMode,
+ loader = new OFFontLoader(fontFileURI, subFontName, embedded, embeddingMode,
encodingMode, useKerning, useAdvanced, resourceResolver);
}
return loader.getFont();
diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java
index 83f6491f3..68be7bed8 100644
--- a/src/java/org/apache/fop/fonts/MultiByteFont.java
+++ b/src/java/org/apache/fop/fonts/MultiByteFont.java
@@ -22,6 +22,7 @@ package org.apache.fop.fonts;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
+import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
@@ -67,6 +68,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
private int firstUnmapped;
private int lastUnmapped;
+ private boolean isOTFFile = false;
+
+ // since for most users the most likely glyphs are in the first cmap segments we store their mapping.
+ private static final int NUM_MOST_LIKELY_GLYPHS = 256;
+ private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS];
+
+ //A map to store each used glyph from the CID set against the glyph name.
+ private LinkedHashMap usedGlyphNames = new LinkedHashMap();
+
/**
* Default constructor
*/
@@ -111,6 +121,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return cidType;
}
+ public void setIsOTFFile(boolean isOTFFile) {
+ this.isOTFFile = isOTFFile;
+ }
+
+ public boolean isOTFFile() {
+ return this.isOTFFile;
+ }
+
/**
* Sets the CIDType.
* @param cidType The cidType to set
@@ -147,6 +165,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return this.cidSet;
}
+ public void mapUsedGlyphName(int gid, String value) {
+ usedGlyphNames.put(gid, value);
+ }
+
+ public LinkedHashMap getUsedGlyphNames() {
+ return usedGlyphNames;
+ }
+
/** {@inheritDoc} */
@Override
public String getEncodingName() {
@@ -177,10 +203,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
* @return the glyph index (or 0 if the glyph is not available)
*/
// [TBD] - needs optimization, i.e., change from linear search to binary search
- private int findGlyphIndex(int c) {
+ public int findGlyphIndex(int c) {
int idx = c;
int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT;
+ // for most users the most likely glyphs are in the first cmap segments (meaning the one with
+ // the lowest unicode start values)
+ if (idx < NUM_MOST_LIKELY_GLYPHS && mostLikelyGlyphs[idx] != 0) {
+ return mostLikelyGlyphs[idx];
+ }
for (int i = 0; (i < cmap.length) && retIdx == 0; i++) {
if (cmap[i].getUnicodeStart() <= idx
&& cmap[i].getUnicodeEnd() >= idx) {
@@ -188,6 +219,9 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
retIdx = cmap[i].getGlyphStartIndex()
+ idx
- cmap[i].getUnicodeStart();
+ if (idx < NUM_MOST_LIKELY_GLYPHS) {
+ mostLikelyGlyphs[idx] = retIdx;
+ }
}
}
return retIdx;
@@ -281,22 +315,6 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return findCharacterFromGlyphIndex(gi, true);
}
-
- /** {@inheritDoc} */
- @Override
- public char mapChar(char c) {
- notifyMapOperation();
- int glyphIndex = findGlyphIndex(c);
- if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- warnMissingGlyph(c);
- glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
- }
- if (isEmbeddable()) {
- glyphIndex = cidSet.mapChar(glyphIndex, c);
- }
- return (char) glyphIndex;
- }
-
protected BitSet getGlyphIndices() {
BitSet bitset = new BitSet();
bitset.set(0);
@@ -327,6 +345,23 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return chars;
}
+ /** {@inheritDoc} */
+ @Override
+ public char mapChar(char c) {
+ notifyMapOperation();
+ int glyphIndex = findGlyphIndex(c);
+ if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
+ warnMissingGlyph(c);
+ if (!isOTFFile) {
+ glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
+ }
+ }
+ if (isEmbeddable()) {
+ glyphIndex = cidSet.mapChar(glyphIndex, c);
+ }
+ return (char) glyphIndex;
+ }
+
/** {@inheritDoc} */
@Override
public boolean hasChar(char c) {
diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java
index cd11c7849..a61d846e6 100644
--- a/src/java/org/apache/fop/fonts/SingleByteFont.java
+++ b/src/java/org/apache/fop/fonts/SingleByteFont.java
@@ -32,7 +32,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.fonts.Glyphs;
import org.apache.fop.apps.io.InternalResourceResolver;
-import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;
+import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
/**
* Generic SingleByte font
diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java
index db858e285..fc003d201 100644
--- a/src/java/org/apache/fop/fonts/apps/TTFReader.java
+++ b/src/java/org/apache/fop/fonts/apps/TTFReader.java
@@ -38,6 +38,7 @@ 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;
// CSOFF: InnerAssignmentCheck
@@ -216,7 +217,8 @@ public class TTFReader extends AbstractFontReader {
InputStream stream = new FileInputStream(fileName);
try {
FontFileReader reader = new FontFileReader(stream);
- boolean supported = ttfFile.readFont(reader, fontName);
+ String header = OFFontLoader.readHeader(reader);
+ boolean supported = ttfFile.readFont(reader, header, fontName);
if (!supported) {
return null;
}
diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
index e115264bb..21ebd4937 100644
--- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
+++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
@@ -43,8 +43,8 @@ import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.FontUtil;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OFFontLoader;
import org.apache.fop.fonts.truetype.TTFFile;
-import org.apache.fop.fonts.truetype.TTFFontLoader;
/**
* Attempts to determine correct FontInfo
@@ -220,7 +220,7 @@ public class FontInfoFinder {
log.debug("Loading " + fontName);
}
try {
- TTFFontLoader ttfLoader = new TTFFontLoader(fontURI, fontName, true,
+ OFFontLoader ttfLoader = new OFFontLoader(fontURI, fontName, true,
EmbeddingMode.AUTO, EncodingMode.AUTO, useKerning, useAdvanced,
resourceResolver);
customFont = ttfLoader.getFont();
diff --git a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java
index f26ac2ffd..90abccaf2 100644
--- a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java
+++ b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java
@@ -31,7 +31,7 @@ import java.util.TreeSet;
*/
public class GlyfTable {
- private final TTFMtxEntry[] mtxTab;
+ private final OFMtxEntry[] mtxTab;
private final long tableOffset;
@@ -47,7 +47,7 @@ public class GlyfTable {
/** All the glyphs that are composed, but do not appear in the subset. */
private Set composedGlyphs = new TreeSet();
- GlyfTable(FontFileReader in, TTFMtxEntry[] metrics, TTFDirTabEntry dirTableEntry,
+ GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry,
Map glyphs) throws IOException {
mtxTab = metrics;
tableOffset = dirTableEntry.getOffset();
diff --git a/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java
new file mode 100644
index 000000000..a9c471d5e
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java
@@ -0,0 +1,119 @@
+/*
+ * 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.truetype;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * This class represents an entry to a TrueType font's Dir Tab.
+ */
+public class OFDirTabEntry {
+
+ private byte[] tag = new byte[4];
+ private int checksum;
+ private long offset;
+ private long length;
+
+ public OFDirTabEntry() {
+ }
+
+ public OFDirTabEntry(long offset, long length) {
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /**
+ * Read Dir Tab.
+ * @param in font file reader
+ * @return tag name
+ * @throws IOException upon I/O exception
+ */
+ public String read(FontFileReader in) throws IOException {
+ tag[0] = in.readTTFByte();
+ tag[1] = in.readTTFByte();
+ tag[2] = in.readTTFByte();
+ tag[3] = in.readTTFByte();
+
+ in.skip(4); // Skip checksum
+
+ offset = in.readTTFULong();
+ length = in.readTTFULong();
+ String tagStr = new String(tag, "ISO-8859-1");
+
+ return tagStr;
+ }
+
+
+ @Override
+ public String toString() {
+ return "Read dir tab ["
+ + tag[0] + " " + tag[1] + " " + tag[2] + " " + tag[3] + "]"
+ + " offset: " + offset
+ + " length: " + length
+ + " name: " + tag;
+ }
+
+ /**
+ * Returns the checksum.
+ * @return int
+ */
+ public int getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * Returns the length.
+ * @return long
+ */
+ public long getLength() {
+ return length;
+ }
+
+ /**
+ * Returns the offset.
+ * @return long
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ /**
+ * Returns the tag bytes.
+ * @return byte[]
+ */
+ public byte[] getTag() {
+ return tag;
+ }
+
+ /**
+ * Returns the tag bytes.
+ * @return byte[]
+ */
+ public String getTagString() {
+ try {
+ return new String(tag, "ISO-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ return this.toString(); // Should never happen.
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
new file mode 100644
index 000000000..f15837bb8
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
@@ -0,0 +1,261 @@
+/*
+ * 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.truetype;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.fop.apps.io.InternalResourceResolver;
+import org.apache.fop.fonts.CIDFontType;
+import org.apache.fop.fonts.CMapSegment;
+import org.apache.fop.fonts.EmbeddingMode;
+import org.apache.fop.fonts.EncodingMode;
+import org.apache.fop.fonts.FontLoader;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.fonts.MultiByteFont;
+import org.apache.fop.fonts.NamedCharacter;
+import org.apache.fop.fonts.SingleByteFont;
+import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
+import org.apache.fop.util.HexEncoder;
+
+/**
+ * Loads a TrueType font into memory directly from the original font file.
+ */
+public class OFFontLoader extends FontLoader {
+
+ private MultiByteFont multiFont;
+ private SingleByteFont singleFont;
+ private final String subFontName;
+ private EncodingMode encodingMode;
+ private EmbeddingMode embeddingMode;
+
+ /**
+ * Default constructor
+ * @param fontFileURI the URI representing the font file
+ * @param resourceResolver the resource resolver for font URI resolution
+ */
+ public OFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) {
+ this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resourceResolver);
+ }
+
+ /**
+ * Additional constructor for TrueType Collections.
+ * @param fontFileURI the URI representing the font file
+ * @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal
+ * TrueType fonts)
+ * @param embedded indicates whether the font is embedded or referenced
+ * @param embeddingMode the embedding mode of the font
+ * @param encodingMode the requested encoding mode
+ * @param useKerning true to enable loading kerning info if available, false to disable
+ * @param useAdvanced true to enable loading advanced info if available, false to disable
+ * @param resolver the FontResolver for font URI resolution
+ */
+ public OFFontLoader(URI fontFileURI, String subFontName, boolean embedded,
+ EmbeddingMode embeddingMode, EncodingMode encodingMode, boolean useKerning,
+ boolean useAdvanced, InternalResourceResolver resolver) {
+ super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
+ this.subFontName = subFontName;
+ this.encodingMode = encodingMode;
+ this.embeddingMode = embeddingMode;
+ if (this.encodingMode == EncodingMode.AUTO) {
+ this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType
+ }
+ if (this.embeddingMode == EmbeddingMode.AUTO) {
+ this.embeddingMode = EmbeddingMode.SUBSET;
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void read() throws IOException {
+ read(this.subFontName);
+ }
+
+ /**
+ * Reads a TrueType font.
+ * @param ttcFontName the TrueType sub-font name of TrueType Collection (may be null for
+ * normal TrueType fonts)
+ * @throws IOException if an I/O error occurs
+ */
+ private void read(String ttcFontName) throws IOException {
+ InputStream in = resourceResolver.getResource(this.fontFileURI);
+ try {
+ FontFileReader reader = new FontFileReader(in);
+ String header = readHeader(reader);
+ boolean isCFF = header.equals("OTTO");
+ OpenFont otf = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced);
+ boolean supported = otf.readFont(reader, header, ttcFontName);
+ if (!supported) {
+ throw new IOException("The font does not have a Unicode cmap table: " + fontFileURI);
+ }
+ buildFont(otf, ttcFontName);
+ loaded = true;
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ public static String readHeader(FontFileReader fontFile) throws IOException {
+ if (fontFile != null) {
+ fontFile.seekSet(0);
+ return fontFile.readTTFString(4); // TTF_FIXED_SIZE (4 bytes)
+ }
+ return null;
+ }
+
+ private void buildFont(OpenFont otf, String ttcFontName) {
+ boolean isCid = this.embedded;
+ if (this.encodingMode == EncodingMode.SINGLE_BYTE) {
+ isCid = false;
+ }
+
+ if (isCid) {
+ multiFont = new MultiByteFont(resourceResolver, embeddingMode);
+ multiFont.setIsOTFFile(otf instanceof OTFFile);
+ returnFont = multiFont;
+ multiFont.setTTCName(ttcFontName);
+ } else {
+ singleFont = new SingleByteFont(resourceResolver);
+ returnFont = singleFont;
+ }
+
+ returnFont.setFontName(otf.getPostScriptName());
+ returnFont.setFullName(otf.getFullName());
+ returnFont.setFamilyNames(otf.getFamilyNames());
+ returnFont.setFontSubFamilyName(otf.getSubFamilyName());
+ returnFont.setCapHeight(otf.getCapHeight());
+ returnFont.setXHeight(otf.getXHeight());
+ returnFont.setAscender(otf.getLowerCaseAscent());
+ returnFont.setDescender(otf.getLowerCaseDescent());
+ returnFont.setFontBBox(otf.getFontBBox());
+ returnFont.setFlags(otf.getFlags());
+ returnFont.setStemV(Integer.parseInt(otf.getStemV())); //not used for TTF
+ returnFont.setItalicAngle(Integer.parseInt(otf.getItalicAngle()));
+ returnFont.setMissingWidth(0);
+ returnFont.setWeight(otf.getWeightClass());
+ returnFont.setEmbeddingMode(this.embeddingMode);
+ if (isCid) {
+ if (otf instanceof OTFFile) {
+ multiFont.setCIDType(CIDFontType.CIDTYPE0);
+ } else {
+ multiFont.setCIDType(CIDFontType.CIDTYPE2);
+ }
+ int[] wx = otf.getWidths();
+ multiFont.setWidthArray(wx);
+ } else {
+ singleFont.setFontType(FontType.TRUETYPE);
+ singleFont.setEncoding(otf.getCharSetName());
+ returnFont.setFirstChar(otf.getFirstChar());
+ returnFont.setLastChar(otf.getLastChar());
+ singleFont.setTrueTypePostScriptVersion(otf.getPostScriptVersion());
+ copyWidthsSingleByte(otf);
+ }
+ returnFont.setCMap(getCMap(otf));
+
+ if (otf.getKerning() != null && useKerning) {
+ copyKerning(otf, isCid);
+ }
+ if (useAdvanced) {
+ copyAdvanced(otf);
+ }
+ if (this.embedded) {
+ if (otf.isEmbeddable()) {
+ returnFont.setEmbedURI(this.fontFileURI);
+ } else {
+ String msg = "The font " + this.fontFileURI + " is not embeddable due to a"
+ + " licensing restriction.";
+ throw new RuntimeException(msg);
+ }
+ }
+ }
+
+ private CMapSegment[] getCMap(OpenFont otf) {
+ CMapSegment[] array = new CMapSegment[otf.getCMaps().size()];
+ return otf.getCMaps().toArray(array);
+ }
+
+ private void copyWidthsSingleByte(OpenFont otf) {
+ int[] wx = otf.getWidths();
+ for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
+ singleFont.setWidth(i, otf.getCharWidth(i));
+ }
+
+ for (CMapSegment segment : otf.getCMaps()) {
+ if (segment.getUnicodeStart() < 0xFFFE) {
+ for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) {
+ int codePoint = singleFont.getEncoding().mapChar(u);
+ if (codePoint <= 0) {
+ int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart();
+ String glyphName = otf.getGlyphName(glyphIndex);
+ if (glyphName.length() == 0 && otf.getPostScriptVersion() != PostScriptVersion.V2) {
+ glyphName = "u" + HexEncoder.encode(u);
+ }
+ if (glyphName.length() > 0) {
+ String unicode = Character.toString(u);
+ NamedCharacter nc = new NamedCharacter(glyphName, unicode);
+ singleFont.addUnencodedCharacter(nc, wx[glyphIndex]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Copy kerning information.
+ */
+ private void copyKerning(OpenFont otf, boolean isCid) {
+
+ // Get kerning
+ Set kerningSet;
+ if (isCid) {
+ kerningSet = otf.getKerning().keySet();
+ } else {
+ kerningSet = otf.getAnsiKerning().keySet();
+ }
+
+ for (Integer kpx1 : kerningSet) {
+ Map h2;
+ if (isCid) {
+ h2 = otf.getKerning().get(kpx1);
+ } else {
+ h2 = otf.getAnsiKerning().get(kpx1);
+ }
+ returnFont.putKerningEntry(kpx1, h2);
+ }
+ }
+
+ /**
+ * Copy advanced typographic information.
+ */
+ private void copyAdvanced(OpenFont otf) {
+ if (returnFont instanceof MultiByteFont) {
+ MultiByteFont mbf = (MultiByteFont) returnFont;
+ mbf.setGDEF(otf.getGDEF());
+ mbf.setGSUB(otf.getGSUB());
+ mbf.setGPOS(otf.getGPOS());
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java b/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java
new file mode 100644
index 000000000..89af2296f
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java
@@ -0,0 +1,195 @@
+/*
+ * 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.truetype;
+
+import java.util.List;
+
+/**
+ * This class represents a TrueType Mtx Entry.
+ */
+class OFMtxEntry {
+
+ private int wx;
+ private int lsb;
+ private String name = "";
+ private int index;
+ private List unicodeIndex = new java.util.ArrayList();
+ private int[] boundingBox = new int[4];
+ private long offset;
+ private byte found = 0;
+
+ /**
+ * Returns a String representation of this object.
+ *
+ * @param t TTFFile to use for unit conversion
+ * @return String String representation
+ */
+ public String toString(TTFFile t) {
+ return "Glyph " + name + " index: " + getIndexAsString() + " bbox ["
+ + t.convertTTFUnit2PDFUnit(boundingBox[0]) + " "
+ + t.convertTTFUnit2PDFUnit(boundingBox[1]) + " "
+ + t.convertTTFUnit2PDFUnit(boundingBox[2]) + " "
+ + t.convertTTFUnit2PDFUnit(boundingBox[3]) + "] wx: "
+ + t.convertTTFUnit2PDFUnit(wx);
+ }
+
+ /**
+ * Returns the boundingBox.
+ * @return int[]
+ */
+ public int[] getBoundingBox() {
+ return boundingBox;
+ }
+
+ /**
+ * Sets the boundingBox.
+ * @param boundingBox The boundingBox to set
+ */
+ public void setBoundingBox(int[] boundingBox) {
+ this.boundingBox = boundingBox;
+ }
+
+ /**
+ * Returns the found.
+ * @return byte
+ */
+ public byte getFound() {
+ return found;
+ }
+
+ /**
+ * Returns the index.
+ * @return int
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Determines whether this index represents a reserved character.
+ * @return True if it is reserved
+ */
+ public boolean isIndexReserved() {
+ return (getIndex() >= 32768) && (getIndex() <= 65535);
+ }
+
+ /**
+ * Returns a String representation of the index taking into account if
+ * the index is in the reserved range.
+ * @return index as String
+ */
+ public String getIndexAsString() {
+ if (isIndexReserved()) {
+ return Integer.toString(getIndex()) + " (reserved)";
+ } else {
+ return Integer.toString(getIndex());
+ }
+ }
+
+ /**
+ * Returns the lsb.
+ * @return int
+ */
+ public int getLsb() {
+ return lsb;
+ }
+
+ /**
+ * Returns the name.
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the offset.
+ * @return long
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ /**
+ * Returns the unicodeIndex.
+ * @return List
+ */
+ public List getUnicodeIndex() {
+ return unicodeIndex;
+ }
+
+ /**
+ * Returns the wx.
+ * @return int
+ */
+ public int getWx() {
+ return wx;
+ }
+
+ /**
+ * Sets the found.
+ * @param found The found to set
+ */
+ public void setFound(byte found) {
+ this.found = found;
+ }
+
+ /**
+ * Sets the index.
+ * @param index The index to set
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Sets the lsb.
+ * @param lsb The lsb to set
+ */
+ public void setLsb(int lsb) {
+ this.lsb = lsb;
+ }
+
+ /**
+ * Sets the name.
+ * @param name The name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Sets the offset.
+ * @param offset The offset to set
+ */
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * Sets the wx.
+ * @param wx The wx to set
+ */
+ public void setWx(int wx) {
+ this.wx = wx;
+ }
+
+
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/OFTableName.java b/src/java/org/apache/fop/fonts/truetype/OFTableName.java
new file mode 100644
index 000000000..f6264129a
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OFTableName.java
@@ -0,0 +1,169 @@
+/*
+ * 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.truetype;
+
+
+/**
+ * Represents table names as found in a TrueType font's Table Directory.
+ * TrueType fonts may have custom tables so we cannot use an enum.
+ */
+public final class OFTableName {
+
+ /** The first table in a TrueType font file containing metadata about other tables. */
+ public static final OFTableName TABLE_DIRECTORY = new OFTableName("tableDirectory");
+
+ /** Baseline data */
+ public static final OFTableName BASE = new OFTableName("BASE");
+
+ /** CFF data/ */
+ public static final OFTableName CFF = new OFTableName("CFF ");
+
+ /** Embedded bitmap data. */
+ public static final OFTableName EBDT = new OFTableName("EBDT");
+
+ /** Embedded bitmap location data. */
+ public static final OFTableName EBLC = new OFTableName("EBLC");
+
+ /** Embedded bitmap scaling data. */
+ public static final OFTableName EBSC = new OFTableName("EBSC");
+
+ /** A FontForge specific table. */
+ public static final OFTableName FFTM = new OFTableName("FFTM");
+
+ /** Divides glyphs into various classes that make using the GPOS/GSUB tables easier. */
+ public static final OFTableName GDEF = new OFTableName("GDEF");
+
+ /** Provides kerning information, mark-to-base, etc. for opentype fonts. */
+ public static final OFTableName GPOS = new OFTableName("GPOS");
+
+ /** Provides ligature information, swash, etc. for opentype fonts. */
+ public static final OFTableName GSUB = new OFTableName("GSUB");
+
+ /** Linear threshold table. */
+ public static final OFTableName LTSH = new OFTableName("LTSH");
+
+ /** OS/2 and Windows specific metrics. */
+ public static final OFTableName OS2 = new OFTableName("OS/2");
+
+ /** PCL 5 data. */
+ public static final OFTableName PCLT = new OFTableName("PCLT");
+
+ /** Vertical Device Metrics table. */
+ public static final OFTableName VDMX = new OFTableName("VDMX");
+
+ /** Character to glyph mapping. */
+ public static final OFTableName CMAP = new OFTableName("cmap");
+
+ /** Control Value Table. */
+ public static final OFTableName CVT = new OFTableName("cvt ");
+
+ /** Font program. */
+ public static final OFTableName FPGM = new OFTableName("fpgm");
+
+ /** Grid-fitting and scan conversion procedure (grayscale). */
+ public static final OFTableName GASP = new OFTableName("gasp");
+
+ /** Glyph data. */
+ public static final OFTableName GLYF = new OFTableName("glyf");
+
+ /** Horizontal device metrics. */
+ public static final OFTableName HDMX = new OFTableName("hdmx");
+
+ /** Font header. */
+ public static final OFTableName HEAD = new OFTableName("head");
+
+ /** Horizontal header. */
+ public static final OFTableName HHEA = new OFTableName("hhea");
+
+ /** Horizontal metrics. */
+ public static final OFTableName HMTX = new OFTableName("hmtx");
+
+ /** Kerning. */
+ public static final OFTableName KERN = new OFTableName("kern");
+
+ /** Index to location. */
+ public static final OFTableName LOCA = new OFTableName("loca");
+
+ /** Maximum profile. */
+ public static final OFTableName MAXP = new OFTableName("maxp");
+
+ /** Naming table. */
+ public static final OFTableName NAME = new OFTableName("name");
+
+ /** PostScript information. */
+ public static final OFTableName POST = new OFTableName("post");
+
+ /** CVT Program. */
+ public static final OFTableName PREP = new OFTableName("prep");
+
+ /** Vertical Metrics header. */
+ public static final OFTableName VHEA = new OFTableName("vhea");
+
+ /** Vertical Metrics. */
+ public static final OFTableName VMTX = new OFTableName("vmtx");
+
+ private final String name;
+
+ private OFTableName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of the table as it should be in the Directory Table.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns an instance of this class corresponding to the given string representation.
+ * @param tableName table name as in the Table Directory
+ * @return TTFTableName
+ */
+ public static OFTableName getValue(String tableName) {
+ if (tableName != null) {
+ return new OFTableName(tableName);
+ }
+ throw new IllegalArgumentException("A TrueType font table name must not be null");
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof OFTableName)) {
+ return false;
+ }
+ OFTableName to = (OFTableName) o;
+ return this.name.equals(to.getName());
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/OTFFile.java b/src/java/org/apache/fop/fonts/truetype/OTFFile.java
new file mode 100644
index 000000000..3976b5994
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OTFFile.java
@@ -0,0 +1,109 @@
+/*
+ * 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.truetype;
+
+import java.io.IOException;
+
+import org.apache.fontbox.cff.CFFDataInput;
+import org.apache.fontbox.cff.CFFFont;
+import org.apache.fontbox.cff.CFFFont.Mapping;
+import org.apache.fontbox.cff.CFFParser;
+
+public class OTFFile extends OpenFont {
+
+ protected CFFFont fileFont;
+
+ public OTFFile() throws IOException {
+ checkForFontbox();
+ }
+
+ private void checkForFontbox() throws IOException {
+ try {
+ Class.forName("org.apache.fontbox.cff.CFFFont");
+ } catch (ClassNotFoundException ex) {
+ throw new IOException("The Fontbox jar was not found in the classpath. This is "
+ + "required for OTF CFF ssupport.");
+ }
+ }
+
+ @Override
+ protected void updateBBoxAndOffset() throws IOException {
+ UnicodeMapping[] mappings = unicodeMappings.toArray(new UnicodeMapping[0]);
+ for (int i = 0; i < mappings.length; i++) {
+ int glyphIdx = mappings[i].getGlyphIndex();
+ Mapping m = fileFont.getGIDMappings().get(glyphIdx);
+ int[] bbox = fileFont.getBoundingBox(m.getSID());
+ String name = fileFont.getNameOfCharFromCode(m.getSID());
+ mtxTab[glyphIdx].setBoundingBox(bbox);
+ mtxTab[glyphIdx].setName(name);
+ }
+ }
+
+ @Override
+ protected void initializeFont(FontFileReader in) throws IOException {
+ fontFile = in;
+ fontFile.seekSet(0);
+ CFFParser parser = new CFFParser();
+ fileFont = parser.parse(in.getAllBytes()).get(0);
+ }
+
+ protected void readName() throws IOException {
+ Object familyName = fileFont.getProperty("FamilyName");
+ if (familyName != null && !familyName.equals("")) {
+ familyNames.add(familyName.toString());
+ fullName = familyName.toString();
+ } else {
+ fullName = fileFont.getName();
+ familyNames.add(fullName);
+ }
+ }
+
+ /**
+ * Reads the CFFData from a given font file
+ * @param fontFile The font file being read
+ * @return The byte data found in the CFF table
+ */
+ public static byte[] getCFFData(FontFileReader fontFile) throws IOException {
+ byte[] cff = new byte[0];
+ CFFDataInput input = new CFFDataInput(fontFile.getAllBytes());
+ input.readBytes(4); //OTTO
+ short numTables = input.readShort();
+ input.readShort(); //searchRange
+ input.readShort(); //entrySelector
+ input.readShort(); //rangeShift
+
+ for (int q = 0; q < numTables; q++) {
+ String tagName = new String(input.readBytes(4));
+ readLong(input); //Checksum
+ long offset = readLong(input);
+ long length = readLong(input);
+ if (tagName.equals("CFF ")) {
+ cff = new byte[(int)length];
+ System.arraycopy(fontFile.getAllBytes(), (int)offset, cff, 0, cff.length);
+ break;
+ }
+ }
+ return cff;
+ }
+
+ private static long readLong(CFFDataInput input) throws IOException {
+ return (input.readCard16() << 16) | input.readCard16();
+ }
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java
new file mode 100644
index 000000000..dbea48216
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java
@@ -0,0 +1,1092 @@
+/*
+ * 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.truetype;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.fontbox.cff.CFFStandardString;
+import org.apache.fontbox.cff.encoding.CFFEncoding;
+
+import org.apache.fop.fonts.MultiByteFont;
+import org.apache.fop.fonts.cff.CFFDataReader;
+import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData;
+import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
+import org.apache.fop.fonts.cff.CFFDataReader.FDSelect;
+import org.apache.fop.fonts.cff.CFFDataReader.FontDict;
+import org.apache.fop.fonts.cff.CFFDataReader.Format0FDSelect;
+import org.apache.fop.fonts.cff.CFFDataReader.Format3FDSelect;
+
+/**
+ * Reads an OpenType CFF file and generates a subset
+ * The OpenType specification can be found at the Microsoft
+ * Typography site: http://www.microsoft.com/typography/otspec/
+ */
+public class OTFSubSetFile extends OTFFile {
+
+ private byte[] output;
+ private int currentPos = 0;
+ private int realSize = 0;
+
+ /** A map containing each glyph to be included in the subset
+ * with their existing and new GID's **/
+ private LinkedHashMap subsetGlyphs;
+
+ /** A map of the new GID to SID used to construct the charset table **/
+ private LinkedHashMap gidToSID;
+
+ private CFFIndexData localIndexSubr;
+ private CFFIndexData globalIndexSubr;
+
+ /** List of subroutines to write to the local / global indexes in the subset font **/
+ private List subsetLocalIndexSubr;
+ private List subsetGlobalIndexSubr;
+
+ /** For fonts which have an FDSelect or ROS flag in Top Dict, this is used to store the
+ * local subroutine indexes for each group as opposed to the above subsetLocalIndexSubr */
+ private ArrayList> fdSubrs;
+
+ /** The subset FD Select table used to store the mappings between glyphs and their
+ * associated FDFont object which point to a private dict and local subroutines. */
+ private LinkedHashMap subsetFDSelect;
+
+ /** A list of unique subroutines from the global / local subroutine indexes */
+ private List localUniques;
+ private List globalUniques;
+
+ /** A store of the number of subroutines each global / local subroutine will store **/
+ private int subsetLocalSubrCount;
+ private int subsetGlobalSubrCount;
+
+ /** A list of char string data for each glyph to be stored in the subset font **/
+ private List subsetCharStringsIndex;
+
+ /** The embedded name to change in the name table **/
+ private String embeddedName;
+
+ /** An array used to hold the string index data for the subset font **/
+ private List stringIndexData = new ArrayList();
+
+ /** The CFF reader object used to read data and offsets from the original font file */
+ private CFFDataReader cffReader = null;
+
+ /** The class used to represent this font **/
+ private MultiByteFont mbFont;
+
+ /** The number of standard strings in CFF **/
+ private static final int NUM_STANDARD_STRINGS = 391;
+ /** The operator used to identify a local subroutine reference */
+ private static final int LOCAL_SUBROUTINE = 10;
+ /** The operator used to identify a global subroutine reference */
+ private static final int GLOBAL_SUBROUTINE = 29;
+
+ public OTFSubSetFile() throws IOException {
+ super();
+ }
+
+ public void readFont(FontFileReader in, String embeddedName, String header,
+ MultiByteFont mbFont) throws IOException {
+ this.mbFont = mbFont;
+ readFont(in, embeddedName, header, mbFont.getUsedGlyphs());
+ }
+
+ /**
+ * Reads and creates a subset of the font.
+ *
+ * @param in FontFileReader to read from
+ * @param name Name to be checked for in the font file
+ * @param header The header of the font file
+ * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
+ * new index as (Integer) value)
+ * @throws IOException in case of an I/O problem
+ */
+ void readFont(FontFileReader in, String embeddedName, String header,
+ Map usedGlyphs) throws IOException {
+ fontFile = in;
+
+ currentPos = 0;
+ realSize = 0;
+
+ this.embeddedName = embeddedName;
+
+ //Sort by the new GID and store in a LinkedHashMap
+ subsetGlyphs = sortByValue(usedGlyphs);
+
+ output = new byte[in.getFileSize()];
+
+ initializeFont(in);
+
+ cffReader = new CFFDataReader(fontFile);
+
+ //Create the CIDFontType0C data
+ createCFF();
+ }
+
+ private LinkedHashMap sortByValue(Map map) {
+ List> list = new ArrayList>(map.entrySet());
+ Collections.sort(list, new Comparator>() {
+ public int compare(Entry o1, Entry o2) {
+ return ((Comparable) o1.getValue()).compareTo(o2.getValue());
+ }
+ });
+
+ LinkedHashMap result = new LinkedHashMap();
+ for (Entry entry : list) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ return result;
+ }
+
+ private void createCFF() throws IOException {
+ //Header
+ writeBytes(cffReader.getHeader());
+
+ //Name Index
+ writeIndex(Arrays.asList(embeddedName.getBytes()));
+
+ //Keep offset of the topDICT so it can be updated once all data has been written
+ int topDictOffset = currentPos;
+ //Top DICT Index and Data
+ byte[] topDictIndex = cffReader.getTopDictIndex().getByteData();
+ int offSize = topDictIndex[2];
+ writeBytes(topDictIndex, 0, 3 + (offSize * 2));
+ int topDictDataOffset = currentPos;
+ writeTopDICT();
+
+ //Create the char string index data and related local / global subroutines
+ if (cffReader.getFDSelect() == null) {
+ createCharStringData();
+ } else {
+ createCharStringDataCID();
+ }
+
+ //If it is a CID-Keyed font, store each FD font and add each SID
+ List fontNameSIDs = null;
+ List subsetFDFonts = null;
+ if (cffReader.getFDSelect() != null) {
+ subsetFDFonts = getUsedFDFonts();
+ fontNameSIDs = storeFDStrings(subsetFDFonts);
+ }
+
+ //String index
+ writeStringIndex();
+
+ //Global subroutine index
+ writeIndex(subsetGlobalIndexSubr);
+
+ //Encoding
+ int encodingOffset = currentPos;
+ writeEncoding(fileFont.getEncoding());
+
+ //Charset table
+ int charsetOffset = currentPos;
+ writeCharsetTable(cffReader.getFDSelect() != null);
+
+ //FDSelect table
+ int fdSelectOffset = currentPos;
+ if (cffReader.getFDSelect() != null) {
+ writeFDSelect();
+ }
+
+ //Char Strings Index
+ int charStringOffset = currentPos;
+ writeIndex(subsetCharStringsIndex);
+
+ if (cffReader.getFDSelect() == null) {
+ //Keep offset to modify later with the local subroutine index offset
+ int privateDictOffset = currentPos;
+ writePrivateDict();
+
+ //Local subroutine index
+ int localIndexOffset = currentPos;
+ writeIndex(subsetLocalIndexSubr);
+
+ //Update the offsets
+ updateOffsets(topDictOffset, charsetOffset, charStringOffset, privateDictOffset,
+ localIndexOffset, encodingOffset);
+ } else {
+ List privateDictOffsets = writeCIDDictsAndSubrs(subsetFDFonts);
+ int fdArrayOffset = writeFDArray(subsetFDFonts, privateDictOffsets, fontNameSIDs);
+
+ updateCIDOffsets(topDictDataOffset, fdArrayOffset, fdSelectOffset, charsetOffset,
+ charStringOffset, encodingOffset);
+ }
+ }
+
+ private List storeFDStrings(List uniqueNewRefs) throws IOException {
+ ArrayList fontNameSIDs = new ArrayList();
+ List fdFonts = cffReader.getFDFonts();
+ for (int i = 0; i < uniqueNewRefs.size(); i++) {
+ FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i));
+ byte[] fdFontByteData = fdFont.getByteData();
+ Map fdFontDict = cffReader.parseDictData(fdFontByteData);
+ fontNameSIDs.add(stringIndexData.size() + NUM_STANDARD_STRINGS);
+ stringIndexData.add(cffReader.getStringIndex().getValue(fdFontDict.get("FontName")
+ .getOperands().get(0).intValue() - NUM_STANDARD_STRINGS));
+ }
+ return fontNameSIDs;
+ }
+
+ private void writeBytes(byte[] out) {
+ for (int i = 0; i < out.length; i++) {
+ output[currentPos++] = out[i];
+ realSize++;
+ }
+ }
+
+ private void writeBytes(byte[] out, int offset, int length) {
+ for (int i = offset; i < offset + length; i++) {
+ output[currentPos++] = out[i];
+ realSize++;
+ }
+ }
+
+ private void writeEncoding(CFFEncoding encoding) throws IOException {
+ LinkedHashMap topDICT = cffReader.getTopDictEntries();
+ DICTEntry encodingEntry = topDICT.get("Encoding");
+ if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0
+ && encodingEntry.getOperands().get(0).intValue() != 1) {
+ writeByte(0);
+ writeByte(gidToSID.size());
+ for (int gid : gidToSID.keySet()) {
+ int code = encoding.getCode(gidToSID.get(gid));
+ writeByte(code);
+ }
+ }
+ }
+
+ private void writeTopDICT() throws IOException {
+ LinkedHashMap topDICT = cffReader.getTopDictEntries();
+ List topDictStringEntries = Arrays.asList("version", "Notice", "Copyright",
+ "FullName", "FamilyName", "Weight", "PostScript");
+ for (Map.Entry dictEntry : topDICT.entrySet()) {
+ String dictKey = dictEntry.getKey();
+ DICTEntry entry = dictEntry.getValue();
+ //If the value is an SID, update the reference but keep the size the same
+ if (dictKey.equals("ROS")) {
+ writeROSEntry(entry);
+ } else if (dictKey.equals("CIDCount")) {
+ writeCIDCount(entry);
+ } else if (topDictStringEntries.contains(dictKey)) {
+ writeTopDictStringEntry(entry);
+ } else {
+ writeBytes(entry.getByteData());
+ }
+ }
+ }
+
+ private void writeROSEntry(DICTEntry dictEntry) throws IOException {
+ int sidA = dictEntry.getOperands().get(0).intValue();
+ if (sidA > 390) {
+ stringIndexData.add(cffReader.getStringIndex().getValue(sidA - NUM_STANDARD_STRINGS));
+ }
+ int sidAStringIndex = stringIndexData.size() + 390;
+ int sidB = dictEntry.getOperands().get(1).intValue();
+ if (sidB > 390) {
+ stringIndexData.add("Identity".getBytes());
+ }
+ int sidBStringIndex = stringIndexData.size() + 390;
+ byte[] cidEntryByteData = dictEntry.getByteData();
+ cidEntryByteData = updateOffset(cidEntryByteData, 0, dictEntry.getOperandLengths().get(0),
+ sidAStringIndex);
+ cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0),
+ dictEntry.getOperandLengths().get(1), sidBStringIndex);
+ cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0)
+ + dictEntry.getOperandLengths().get(1), dictEntry.getOperandLengths().get(2), 139);
+ writeBytes(cidEntryByteData);
+ }
+
+ private void writeCIDCount(DICTEntry dictEntry) throws IOException {
+ byte[] cidCountByteData = dictEntry.getByteData();
+ cidCountByteData = updateOffset(cidCountByteData, 0, dictEntry.getOperandLengths().get(0),
+ subsetGlyphs.size());
+ writeBytes(cidCountByteData);
+ }
+
+ private void writeTopDictStringEntry(DICTEntry dictEntry) throws IOException {
+ int sid = dictEntry.getOperands().get(0).intValue();
+ if (sid > 391) {
+ stringIndexData.add(cffReader.getStringIndex().getValue(sid - 391));
+ }
+
+ byte[] newDictEntry = createNewRef(stringIndexData.size() + 390, dictEntry.getOperator(),
+ dictEntry.getOperandLength());
+ writeBytes(newDictEntry);
+ }
+
+ private void writeStringIndex() throws IOException {
+ Map topDICT = cffReader.getTopDictEntries();
+ int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue();
+
+ gidToSID = new LinkedHashMap();
+
+ for (int gid : subsetGlyphs.keySet()) {
+ int sid = cffReader.getSIDFromGID(charsetOffset, gid);
+ //Check whether the SID falls into the standard string set
+ if (sid < NUM_STANDARD_STRINGS) {
+ gidToSID.put(subsetGlyphs.get(gid), sid);
+ if (mbFont != null) {
+ mbFont.mapUsedGlyphName(subsetGlyphs.get(gid),
+ CFFStandardString.getName(sid));
+ }
+ } else {
+ int index = sid - NUM_STANDARD_STRINGS;
+ if (index <= cffReader.getStringIndex().getNumObjects()) {
+ if (mbFont != null) {
+ mbFont.mapUsedGlyphName(subsetGlyphs.get(gid),
+ new String(cffReader.getStringIndex().getValue(index)));
+ }
+ gidToSID.put(subsetGlyphs.get(gid), stringIndexData.size() + 391);
+ stringIndexData.add(cffReader.getStringIndex().getValue(index));
+ } else {
+ if (mbFont != null) {
+ mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), ".notdef");
+ }
+ gidToSID.put(subsetGlyphs.get(gid), index);
+ }
+ }
+ }
+ //Write the String Index
+ writeIndex(stringIndexData);
+ }
+
+ private void createCharStringDataCID() throws IOException {
+ CFFIndexData charStringsIndex = cffReader.getCharStringIndex();
+
+ FDSelect fontDictionary = cffReader.getFDSelect();
+ if (fontDictionary instanceof Format0FDSelect) {
+ throw new UnsupportedOperationException("OTF CFF CID Format0 currently not implemented");
+ } else if (fontDictionary instanceof Format3FDSelect) {
+ Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary;
+ Map subsetGroups = new HashMap();
+
+ List uniqueGroups = new ArrayList();
+ for (int gid : subsetGlyphs.keySet()) {
+ Integer[] ranges = fdSelect.getRanges().keySet().toArray(new Integer[0]);
+ for (int i = 0; i < ranges.length; i++) {
+ int nextRange = -1;
+ if (i < ranges.length - 1) {
+ nextRange = ranges[i + 1];
+ } else {
+ nextRange = fdSelect.getSentinelGID();
+ }
+ if (gid >= ranges[i] && gid < nextRange) {
+ subsetGroups.put(gid, fdSelect.getRanges().get(ranges[i]));
+ if (!uniqueGroups.contains(fdSelect.getRanges().get(ranges[i]))) {
+ uniqueGroups.add(fdSelect.getRanges().get(ranges[i]));
+ }
+ }
+ }
+ }
+
+ //Prepare resources
+ globalIndexSubr = cffReader.getGlobalIndexSubr();
+
+ //Create the new char string index
+ subsetCharStringsIndex = new ArrayList();
+
+ globalUniques = new ArrayList();
+
+ subsetFDSelect = new LinkedHashMap();
+
+ List> foundLocalUniques = new ArrayList>();
+ for (int i = 0; i < uniqueGroups.size(); i++) {
+ foundLocalUniques.add(new ArrayList());
+ }
+ for (int gid : subsetGlyphs.keySet()) {
+ int group = subsetGroups.get(gid);
+ localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData();
+ localUniques = foundLocalUniques.get(uniqueGroups.indexOf(subsetGroups.get(gid)));
+
+ FDIndexReference newFDReference = new FDIndexReference(
+ uniqueGroups.indexOf(subsetGroups.get(gid)), subsetGroups.get(gid));
+ subsetFDSelect.put(subsetGlyphs.get(gid), newFDReference);
+ byte[] data = charStringsIndex.getValue(gid);
+ preScanForSubsetIndexSize(data);
+ }
+
+ //Create the two lists which are to store the local and global subroutines
+ subsetGlobalIndexSubr = new ArrayList();
+
+ fdSubrs = new ArrayList>();
+ subsetGlobalSubrCount = globalUniques.size();
+ globalUniques.clear();
+ localUniques = null;
+
+ for (int l = 0; l < foundLocalUniques.size(); l++) {
+ fdSubrs.add(new ArrayList());
+ }
+ List> foundLocalUniquesB = new ArrayList>();
+ for (int k = 0; k < uniqueGroups.size(); k++) {
+ foundLocalUniquesB.add(new ArrayList());
+ }
+ for (Integer gid : subsetGlyphs.keySet()) {
+ int group = subsetGroups.get(gid);
+ localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData();
+ localUniques = foundLocalUniquesB.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex());
+ byte[] data = charStringsIndex.getValue(gid);
+ subsetLocalIndexSubr = fdSubrs.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex());
+ subsetLocalSubrCount = foundLocalUniques.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()).size();
+ data = readCharStringData(data, subsetLocalSubrCount);
+ subsetCharStringsIndex.add(data);
+ }
+ }
+ }
+
+ private void writeFDSelect() {
+ writeByte(0); //Format
+ for (Integer gid : subsetFDSelect.keySet()) {
+ writeByte(subsetFDSelect.get(gid).getNewFDIndex());
+ }
+ }
+
+ private List getUsedFDFonts() {
+ List uniqueNewRefs = new ArrayList();
+ for (int gid : subsetFDSelect.keySet()) {
+ int fdIndex = subsetFDSelect.get(gid).getOldFDIndex();
+ if (!uniqueNewRefs.contains(fdIndex)) {
+ uniqueNewRefs.add(fdIndex);
+ }
+ }
+ return uniqueNewRefs;
+ }
+
+ private List writeCIDDictsAndSubrs(List uniqueNewRefs)
+ throws IOException {
+ List privateDictOffsets = new ArrayList();
+ List fdFonts = cffReader.getFDFonts();
+ for (int i = 0; i < uniqueNewRefs.size(); i++) {
+ FontDict curFDFont = fdFonts.get(uniqueNewRefs.get(i));
+ HashMap fdPrivateDict = cffReader.parseDictData(
+ curFDFont.getPrivateDictData());
+ int privateDictOffset = currentPos;
+ privateDictOffsets.add(privateDictOffset);
+ byte[] fdPrivateDictByteData = curFDFont.getPrivateDictData();
+ if (fdPrivateDict.get("Subrs") != null) {
+ fdPrivateDictByteData = updateOffset(fdPrivateDictByteData, fdPrivateDict.get("Subrs").getOffset(),
+ fdPrivateDict.get("Subrs").getOperandLength(),
+ fdPrivateDictByteData.length);
+ }
+ writeBytes(fdPrivateDictByteData);
+ writeIndex(fdSubrs.get(i));
+ }
+ return privateDictOffsets;
+ }
+
+ private int writeFDArray(List uniqueNewRefs, List privateDictOffsets,
+ List fontNameSIDs)
+ throws IOException {
+ int offset = currentPos;
+ List fdFonts = cffReader.getFDFonts();
+
+ writeCard16(uniqueNewRefs.size());
+ writeByte(1); //Offset size
+ writeByte(1); //First offset
+
+ int count = 1;
+ for (int i = 0; i < uniqueNewRefs.size(); i++) {
+ FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i));
+ count += fdFont.getByteData().length;
+ writeByte(count);
+ }
+
+ for (int i = 0; i < uniqueNewRefs.size(); i++) {
+ FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i));
+ byte[] fdFontByteData = fdFont.getByteData();
+ Map fdFontDict = cffReader.parseDictData(fdFontByteData);
+ //Update the SID to the FontName
+ fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("FontName").getOffset() - 1,
+ fdFontDict.get("FontName").getOperandLengths().get(0),
+ fontNameSIDs.get(i));
+ //Update the Private dict reference
+ fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("Private").getOffset()
+ + fdFontDict.get("Private").getOperandLengths().get(0),
+ fdFontDict.get("Private").getOperandLengths().get(1),
+ privateDictOffsets.get(i));
+ writeBytes(fdFontByteData);
+ }
+ return offset;
+ }
+
+ private class FDIndexReference {
+ private int newFDIndex;
+ private int oldFDIndex;
+
+ public FDIndexReference(int newFDIndex, int oldFDIndex) {
+ this.newFDIndex = newFDIndex;
+ this.oldFDIndex = oldFDIndex;
+ }
+
+ public int getNewFDIndex() {
+ return newFDIndex;
+ }
+
+ public int getOldFDIndex() {
+ return oldFDIndex;
+ }
+ }
+
+ private void createCharStringData() throws IOException {
+ Map topDICT = cffReader.getTopDictEntries();
+
+ CFFIndexData charStringsIndex = cffReader.getCharStringIndex();
+
+ DICTEntry privateEntry = topDICT.get("Private");
+ if (privateEntry != null) {
+ int privateOffset = privateEntry.getOperands().get(1).intValue();
+ Map privateDICT = cffReader.getPrivateDict(privateEntry);
+
+ int localSubrOffset = privateOffset + privateDICT.get("Subrs").getOperands().get(0).intValue();
+ localIndexSubr = cffReader.readIndex(localSubrOffset);
+ }
+
+ globalIndexSubr = cffReader.getGlobalIndexSubr();
+
+ //Create the two lists which are to store the local and global subroutines
+ subsetLocalIndexSubr = new ArrayList();
+ subsetGlobalIndexSubr = new ArrayList();
+
+ //Create the new char string index
+ subsetCharStringsIndex = new ArrayList();
+
+ localUniques = new ArrayList();
+ globalUniques = new ArrayList();
+
+ for (int gid : subsetGlyphs.keySet()) {
+ byte[] data = charStringsIndex.getValue(gid);
+ preScanForSubsetIndexSize(data);
+ }
+
+ //Store the size of each subset index and clear the unique arrays
+ subsetLocalSubrCount = localUniques.size();
+ subsetGlobalSubrCount = globalUniques.size();
+ localUniques.clear();
+ globalUniques.clear();
+
+ for (int gid : subsetGlyphs.keySet()) {
+ byte[] data = charStringsIndex.getValue(gid);
+ //Retrieve modified char string data and fill local / global subroutine arrays
+ data = readCharStringData(data, subsetLocalSubrCount);
+ subsetCharStringsIndex.add(data);
+ }
+ }
+
+ private void preScanForSubsetIndexSize(byte[] data) throws IOException {
+ boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
+ boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
+ BytesNumber operand = new BytesNumber(-1, -1);
+ for (int dataPos = 0; dataPos < data.length; dataPos++) {
+ int b0 = data[dataPos] & 0xff;
+ if (b0 == LOCAL_SUBROUTINE && hasLocalSubroutines) {
+ int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber());
+
+ if (!localUniques.contains(subrNumber) && subrNumber < localIndexSubr.getNumObjects()) {
+ localUniques.add(subrNumber);
+ byte[] subr = localIndexSubr.getValue(subrNumber);
+ preScanForSubsetIndexSize(subr);
+ }
+ operand.clearNumber();
+ } else if (b0 == GLOBAL_SUBROUTINE && hasGlobalSubroutines) {
+ int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber());
+
+ if (!globalUniques.contains(subrNumber) && subrNumber < globalIndexSubr.getNumObjects()) {
+ globalUniques.add(subrNumber);
+ byte[] subr = globalIndexSubr.getValue(subrNumber);
+ preScanForSubsetIndexSize(subr);
+ }
+ operand.clearNumber();
+ } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) {
+ operand.clearNumber();
+ if (b0 == 19 || b0 == 20) {
+ dataPos += 1;
+ }
+ } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
+ operand = readNumber(b0, data, dataPos);
+ dataPos += operand.getNumBytes() - 1;
+ }
+ }
+ }
+
+ private int getSubrNumber(int numSubroutines, int operand) {
+ int bias = getBias(numSubroutines);
+ return bias + operand;
+ }
+
+ private byte[] readCharStringData(byte[] data, int subsetLocalSubrCount) throws IOException {
+ boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
+ boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
+ BytesNumber operand = new BytesNumber(-1, -1);
+ for (int dataPos = 0; dataPos < data.length; dataPos++) {
+ int b0 = data[dataPos] & 0xff;
+ if (b0 == 10 && hasLocalSubroutines) {
+ int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber());
+
+ int newRef = getNewRefForReference(subrNumber, localUniques, localIndexSubr, subsetLocalIndexSubr,
+ subsetLocalSubrCount);
+
+ if (newRef != -1) {
+ byte[] newData = constructNewRefData(dataPos, data, operand, subsetLocalSubrCount,
+ newRef, new int[] {10});
+ dataPos -= data.length - newData.length;
+ data = newData;
+ }
+
+ operand.clearNumber();
+ } else if (b0 == 29 && hasGlobalSubroutines) {
+ int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber());
+
+ int newRef = getNewRefForReference(subrNumber, globalUniques, globalIndexSubr, subsetGlobalIndexSubr,
+ subsetGlobalSubrCount);
+
+ if (newRef != -1) {
+ byte[] newData = constructNewRefData(dataPos, data, operand, subsetGlobalSubrCount,
+ newRef, new int[] {29});
+ dataPos -= (data.length - newData.length);
+ data = newData;
+ }
+
+ operand.clearNumber();
+ } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) {
+ operand.clearNumber();
+ if (b0 == 19 || b0 == 20) {
+ dataPos += 1;
+ }
+ } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
+ operand = readNumber(b0, data, dataPos);
+ dataPos += operand.getNumBytes() - 1;
+ }
+ }
+
+ //Return the data with the modified references to our arrays
+ return data;
+ }
+
+ private int getNewRefForReference(int subrNumber, List uniquesArray,
+ CFFIndexData indexSubr, List subsetIndexSubr, int subrCount) throws IOException {
+ int newRef = -1;
+ if (!uniquesArray.contains(subrNumber)) {
+ if (subrNumber < indexSubr.getNumObjects()) {
+ byte[] subr = indexSubr.getValue(subrNumber);
+ subr = readCharStringData(subr, subrCount);
+ if (!uniquesArray.contains(subrNumber)) {
+ uniquesArray.add(subrNumber);
+ subsetIndexSubr.add(subr);
+ newRef = subsetIndexSubr.size() - 1;
+ } else {
+ newRef = uniquesArray.indexOf(subrNumber);
+ }
+ }
+ } else {
+ newRef = uniquesArray.indexOf(subrNumber);
+ }
+ return newRef;
+ }
+
+ private int getBias(int subrCount) {
+ if (subrCount < 1240) {
+ return 107;
+ } else if (subrCount < 33900) {
+ return 1131;
+ } else {
+ return 32768;
+ }
+ }
+
+ private byte[] constructNewRefData(int curDataPos, byte[] currentData, BytesNumber operand,
+ int fullSubsetIndexSize, int curSubsetIndexSize, int[] operatorCode) {
+ //Create the new array with the modified reference
+ byte[] newData;
+ int startRef = curDataPos - operand.getNumBytes();
+ int length = operand.getNumBytes() + 1;
+ byte[] preBytes = new byte[startRef];
+ System.arraycopy(currentData, 0, preBytes, 0, startRef);
+ int newBias = getBias(fullSubsetIndexSize);
+ int newRef = curSubsetIndexSize - newBias;
+ byte[] newRefBytes = createNewRef(newRef, operatorCode, -1);
+ newData = concatArray(preBytes, newRefBytes);
+ byte[] postBytes = new byte[currentData.length - (startRef + length)];
+ System.arraycopy(currentData, startRef + length, postBytes, 0,
+ currentData.length - (startRef + length));
+ return concatArray(newData, postBytes);
+ }
+
+ public static byte[] createNewRef(int newRef, int[] operatorCode, int forceLength) {
+ byte[] newRefBytes;
+ int sizeOfOperator = operatorCode.length;
+ if ((forceLength == -1 && newRef <= 107) || forceLength == 1) {
+ newRefBytes = new byte[1 + sizeOfOperator];
+ //The index values are 0 indexed
+ newRefBytes[0] = (byte)(newRef + 139);
+ for (int i = 0; i < operatorCode.length; i++) {
+ newRefBytes[1 + i] = (byte)operatorCode[i];
+ }
+ } else if ((forceLength == -1 && newRef <= 1131) || forceLength == 2) {
+ newRefBytes = new byte[2 + sizeOfOperator];
+ if (newRef <= 363) {
+ newRefBytes[0] = (byte)247;
+ } else if (newRef <= 619) {
+ newRefBytes[0] = (byte)248;
+ } else if (newRef <= 875) {
+ newRefBytes[0] = (byte)249;
+ } else {
+ newRefBytes[0] = (byte)250;
+ }
+ newRefBytes[1] = (byte)(newRef - 108);
+ for (int i = 0; i < operatorCode.length; i++) {
+ newRefBytes[2 + i] = (byte)operatorCode[i];
+ }
+ } else if ((forceLength == -1 && newRef <= 32767) || forceLength == 3) {
+ newRefBytes = new byte[3 + sizeOfOperator];
+ newRefBytes[0] = 28;
+ newRefBytes[1] = (byte)(newRef >> 8);
+ newRefBytes[2] = (byte)newRef;
+ for (int i = 0; i < operatorCode.length; i++) {
+ newRefBytes[3 + i] = (byte)operatorCode[i];
+ }
+ } else {
+ newRefBytes = new byte[5 + sizeOfOperator];
+ newRefBytes[0] = 29;
+ newRefBytes[1] = (byte)(newRef >> 24);
+ newRefBytes[2] = (byte)(newRef >> 16);
+ newRefBytes[3] = (byte)(newRef >> 8);
+ newRefBytes[4] = (byte)newRef;
+ for (int i = 0; i < operatorCode.length; i++) {
+ newRefBytes[5 + i] = (byte)operatorCode[i];
+ }
+ }
+ return newRefBytes;
+ }
+
+ public static byte[] concatArray(byte[] a, byte[] b) {
+ int aLen = a.length;
+ int bLen = b.length;
+ byte[] c = new byte[aLen + bLen];
+ System.arraycopy(a, 0, c, 0, aLen);
+ System.arraycopy(b, 0, c, aLen, bLen);
+ return c;
+ }
+
+ private int writeIndex(List dataArray) {
+ int hdrTotal = 3;
+ //2 byte number of items
+ this.writeCard16(dataArray.size());
+ //Offset Size: 1 byte = 256, 2 bytes = 65536 etc.
+ int totLength = 0;
+ for (int i = 0; i < dataArray.size(); i++) {
+ totLength += dataArray.get(i).length;
+ }
+ int offSize = 1;
+ if (totLength <= (1 << 8)) {
+ offSize = 1;
+ } else if (totLength <= (1 << 16)) {
+ offSize = 2;
+ } else if (totLength <= (1 << 24)) {
+ offSize = 3;
+ } else {
+ offSize = 4;
+ }
+ this.writeByte(offSize);
+ //Count the first offset 1
+ hdrTotal += offSize;
+ int total = 0;
+ for (int i = 0; i < dataArray.size(); i++) {
+ hdrTotal += offSize;
+ int length = dataArray.get(i).length;
+ switch (offSize) {
+ case 1:
+ if (i == 0) {
+ writeByte(1);
+ }
+ total += length;
+ writeByte(total + 1);
+ break;
+ case 2:
+ if (i == 0) {
+ writeCard16(1);
+ }
+ total += length;
+ writeCard16(total + 1);
+ break;
+ case 3:
+ if (i == 0) {
+ writeThreeByteNumber(1);
+ }
+ total += length;
+ writeThreeByteNumber(total + 1);
+ break;
+ case 4:
+ if (i == 0) {
+ writeULong(1);
+ }
+ total += length;
+ writeULong(total + 1);
+ break;
+ default:
+ throw new AssertionError("Offset Size was not an expected value.");
+ }
+ }
+ for (int i = 0; i < dataArray.size(); i++) {
+ writeBytes(dataArray.get(i));
+ }
+ return hdrTotal + total;
+ }
+
+
+ private BytesNumber readNumber(int b0, byte[] input, int curPos) throws IOException {
+ if (b0 == 28) {
+ int b1 = input[curPos + 1] & 0xff;
+ int b2 = input[curPos + 2] & 0xff;
+ return new BytesNumber(Integer.valueOf((short) (b1 << 8 | b2)), 3);
+ } else if (b0 >= 32 && b0 <= 246) {
+ return new BytesNumber(Integer.valueOf(b0 - 139), 1);
+ } else if (b0 >= 247 && b0 <= 250) {
+ int b1 = input[curPos + 1] & 0xff;
+ return new BytesNumber(Integer.valueOf((b0 - 247) * 256 + b1 + 108), 2);
+ } else if (b0 >= 251 && b0 <= 254) {
+ int b1 = input[curPos + 1] & 0xff;
+ return new BytesNumber(Integer.valueOf(-(b0 - 251) * 256 - b1 - 108), 2);
+ } else if (b0 == 255) {
+ int b1 = input[curPos + 1] & 0xff;
+ int b2 = input[curPos + 2] & 0xff;
+ return new BytesNumber(Integer.valueOf((short)(b1 << 8 | b2)), 5);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * A class used to store the last number operand and also it's size in bytes
+ */
+ private static final class BytesNumber {
+ private int number;
+ private int numBytes;
+
+ public BytesNumber(int number, int numBytes) {
+ this.number = number;
+ this.numBytes = numBytes;
+ }
+
+ public int getNumber() {
+ return this.number;
+ }
+
+ public int getNumBytes() {
+ return this.numBytes;
+ }
+
+ public void clearNumber() {
+ this.number = -1;
+ this.numBytes = -1;
+ }
+ }
+
+ private void writeCharsetTable(boolean cidFont) throws IOException {
+ writeByte(0);
+ for (int gid : gidToSID.keySet()) {
+ if (cidFont && gid == 0) {
+ continue;
+ }
+ writeCard16((cidFont) ? gid : gidToSID.get(gid));
+ }
+ }
+
+ private void writePrivateDict() throws IOException {
+ Map topDICT = cffReader.getTopDictEntries();
+
+ DICTEntry privateEntry = topDICT.get("Private");
+ if (privateEntry != null) {
+ writeBytes(cffReader.getPrivateDictBytes(privateEntry));
+ }
+ }
+
+ private void updateOffsets(int topDictOffset, int charsetOffset, int charStringOffset,
+ int privateDictOffset, int localIndexOffset, int encodingOffset)
+ throws IOException {
+ Map topDICT = cffReader.getTopDictEntries();
+ Map privateDICT = null;
+
+ DICTEntry privateEntry = topDICT.get("Private");
+ if (privateEntry != null) {
+ privateDICT = cffReader.getPrivateDict(privateEntry);
+ }
+
+ int dataPos = 3 + (cffReader.getTopDictIndex().getOffSize()
+ * cffReader.getTopDictIndex().getOffsets().length);
+ int dataTopDictOffset = topDictOffset + dataPos;
+
+ updateFixedOffsets(topDICT, dataTopDictOffset, charsetOffset, charStringOffset, encodingOffset);
+
+ if (privateDICT != null) {
+ //Private index offset in the top dict
+ int oldPrivateOffset = dataTopDictOffset + privateEntry.getOffset();
+ output = updateOffset(output, oldPrivateOffset + privateEntry.getOperandLengths().get(0),
+ privateEntry.getOperandLengths().get(1), privateDictOffset);
+
+ //Update the local subroutine index offset in the private dict
+ DICTEntry subroutines = privateDICT.get("Subrs");
+ int oldLocalSubrOffset = privateDictOffset + subroutines.getOffset();
+ //Value needs to be converted to -139 etc.
+ int encodeValue = 0;
+ if (subroutines.getOperandLength() == 1) {
+ encodeValue = 139;
+ }
+ output = updateOffset(output, oldLocalSubrOffset, subroutines.getOperandLength(),
+ (localIndexOffset - privateDictOffset) + encodeValue);
+ }
+ }
+
+ private void updateFixedOffsets(Map topDICT, int dataTopDictOffset,
+ int charsetOffset, int charStringOffset, int encodingOffset) {
+ //Charset offset in the top dict
+ DICTEntry charset = topDICT.get("charset");
+ int oldCharsetOffset = dataTopDictOffset + charset.getOffset();
+ output = updateOffset(output, oldCharsetOffset, charset.getOperandLength(), charsetOffset);
+
+ //Char string index offset in the private dict
+ DICTEntry charString = topDICT.get("CharStrings");
+ int oldCharStringOffset = dataTopDictOffset + charString.getOffset();
+ output = updateOffset(output, oldCharStringOffset, charString.getOperandLength(), charStringOffset);
+
+ DICTEntry encodingEntry = topDICT.get("Encoding");
+ if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0
+ && encodingEntry.getOperands().get(0).intValue() != 1) {
+ int oldEncodingOffset = dataTopDictOffset + encodingEntry.getOffset();
+ output = updateOffset(output, oldEncodingOffset, encodingEntry.getOperandLength(), encodingOffset);
+ }
+ }
+
+ private void updateCIDOffsets(int topDictDataOffset, int fdArrayOffset, int fdSelectOffset,
+ int charsetOffset, int charStringOffset, int encodingOffset) {
+ LinkedHashMap topDict = cffReader.getTopDictEntries();
+
+ DICTEntry fdArrayEntry = topDict.get("FDArray");
+ if (fdArrayEntry != null) {
+ output = updateOffset(output, topDictDataOffset + fdArrayEntry.getOffset() - 1,
+ fdArrayEntry.getOperandLength(), fdArrayOffset);
+ }
+
+ DICTEntry fdSelect = topDict.get("FDSelect");
+ if (fdSelect != null) {
+ output = updateOffset(output, topDictDataOffset + fdSelect.getOffset() - 1,
+ fdSelect.getOperandLength(), fdSelectOffset);
+ }
+
+ updateFixedOffsets(topDict, topDictDataOffset, charsetOffset, charStringOffset, encodingOffset);
+ }
+
+ private byte[] updateOffset(byte[] out, int position, int length, int replacement) {
+ switch (length) {
+ case 1:
+ out[position] = (byte)(replacement & 0xFF);
+ break;
+ case 2:
+ if (replacement <= 363) {
+ out[position] = (byte)247;
+ } else if (replacement <= 619) {
+ out[position] = (byte)248;
+ } else if (replacement <= 875) {
+ out[position] = (byte)249;
+ } else {
+ out[position] = (byte)250;
+ }
+ out[position + 1] = (byte)(replacement - 108);
+ break;
+ case 3:
+ out[position] = (byte)28;
+ out[position + 1] = (byte)((replacement >> 8) & 0xFF);
+ out[position + 2] = (byte)(replacement & 0xFF);
+ break;
+ case 5:
+ out[position] = (byte)29;
+ out[position + 1] = (byte)((replacement >> 24) & 0xFF);
+ out[position + 2] = (byte)((replacement >> 16) & 0xFF);
+ out[position + 3] = (byte)((replacement >> 8) & 0xFF);
+ out[position + 4] = (byte)(replacement & 0xFF);
+ break;
+ default:
+ }
+ return out;
+ }
+
+ /**
+ * Appends a byte to the output array,
+ * updates currentPost but not realSize
+ */
+ private void writeByte(int b) {
+ output[currentPos++] = (byte)b;
+ realSize++;
+ }
+
+ /**
+ * Appends a USHORT to the output array,
+ * updates currentPost but not realSize
+ */
+ private void writeCard16(int s) {
+ byte b1 = (byte)((s >> 8) & 0xff);
+ byte b2 = (byte)(s & 0xff);
+ writeByte(b1);
+ writeByte(b2);
+ }
+
+ private void writeThreeByteNumber(int s) {
+ byte b1 = (byte)((s >> 16) & 0xFF);
+ byte b2 = (byte)((s >> 8) & 0xFF);
+ byte b3 = (byte)(s & 0xFF);
+ output[currentPos++] = b1;
+ output[currentPos++] = b2;
+ output[currentPos++] = b3;
+ realSize += 3;
+ }
+
+ /**
+ * Appends a ULONG to the output array,
+ * at the given position
+ */
+ private void writeULong(int s) {
+ byte b1 = (byte)((s >> 24) & 0xff);
+ byte b2 = (byte)((s >> 16) & 0xff);
+ byte b3 = (byte)((s >> 8) & 0xff);
+ byte b4 = (byte)(s & 0xff);
+ output[currentPos++] = b1;
+ output[currentPos++] = b2;
+ output[currentPos++] = b3;
+ output[currentPos++] = b4;
+ realSize += 4;
+ }
+
+ /**
+ * Returns a subset of the fonts (readFont() MUST be called first in order to create the
+ * subset).
+ * @return byte array
+ */
+ public byte[] getFontSubset() {
+ byte[] ret = new byte[realSize];
+ System.arraycopy(output, 0, ret, 0, realSize);
+ return ret;
+ }
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/OpenFont.java b/src/java/org/apache/fop/fonts/truetype/OpenFont.java
new file mode 100644
index 000000000..ce9a2b388
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/OpenFont.java
@@ -0,0 +1,1937 @@
+/*
+ * 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.truetype;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.xmlgraphics.fonts.Glyphs;
+
+import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader;
+import org.apache.fop.fonts.CMapSegment;
+import org.apache.fop.fonts.FontUtil;
+import org.apache.fop.fonts.MultiByteFont;
+
+public abstract class OpenFont {
+
+ static final byte NTABS = 24;
+ static final int MAX_CHAR_CODE = 255;
+ static final int ENC_BUF_SIZE = 1024;
+
+ private static final String[] MAC_GLYPH_ORDERING = {
+ /* 0x000 */
+ ".notdef", ".null", "nonmarkingreturn", "space",
+ "exclam", "quotedbl", "numbersign", "dollar",
+ "percent", "ampersand", "quotesingle", "parenleft",
+ "parenright", "asterisk", "plus", "comma",
+ /* 0x010 */
+ "hyphen", "period", "slash", "zero",
+ "one", "two", "three", "four",
+ "five", "six", "seven", "eight",
+ "nine", "colon", "semicolon", "less",
+ /* 0x020 */
+ "equal", "greater", "question", "at",
+ "A", "B", "C", "D",
+ "E", "F", "G", "H",
+ "I", "J", "K", "L",
+ /* 0x030 */
+ "M", "N", "O", "P",
+ "Q", "R", "S", "T",
+ "U", "V", "W", "X",
+ "Y", "Z", "bracketleft", "backslash",
+ /* 0x040 */
+ "bracketright", "asciicircum", "underscore", "grave",
+ "a", "b", "c", "d",
+ "e", "f", "g", "h",
+ "i", "j", "k", "l",
+ /* 0x050 */
+ "m", "n", "o", "p",
+ "q", "r", "s", "t",
+ "u", "v", "w", "x",
+ "y", "z", "braceleft", "bar",
+ /* 0x060 */
+ "braceright", "asciitilde", "Adieresis", "Aring",
+ "Ccedilla", "Eacute", "Ntilde", "Odieresis",
+ "Udieresis", "aacute", "agrave", "acircumflex",
+ "adieresis", "atilde", "aring", "ccedilla",
+ /* 0x070 */
+ "eacute", "egrave", "ecircumflex", "edieresis",
+ "iacute", "igrave", "icircumflex", "idieresis",
+ "ntilde", "oacute", "ograve", "ocircumflex",
+ "odieresis", "otilde", "uacute", "ugrave",
+ /* 0x080 */
+ "ucircumflex", "udieresis", "dagger", "degree",
+ "cent", "sterling", "section", "bullet",
+ "paragraph", "germandbls", "registered", "copyright",
+ "trademark", "acute", "dieresis", "notequal",
+ /* 0x090 */
+ "AE", "Oslash", "infinity", "plusminus",
+ "lessequal", "greaterequal", "yen", "mu",
+ "partialdiff", "summation", "product", "pi",
+ "integral", "ordfeminine", "ordmasculine", "Omega",
+ /* 0x0A0 */
+ "ae", "oslash", "questiondown", "exclamdown",
+ "logicalnot", "radical", "florin", "approxequal",
+ "Delta", "guillemotleft", "guillemotright", "ellipsis",
+ "nonbreakingspace", "Agrave", "Atilde", "Otilde",
+ /* 0x0B0 */
+ "OE", "oe", "endash", "emdash",
+ "quotedblleft", "quotedblright", "quoteleft", "quoteright",
+ "divide", "lozenge", "ydieresis", "Ydieresis",
+ "fraction", "currency", "guilsinglleft", "guilsinglright",
+ /* 0x0C0 */
+ "fi", "fl", "daggerdbl", "periodcentered",
+ "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
+ "Ecircumflex", "Aacute", "Edieresis", "Egrave",
+ "Iacute", "Icircumflex", "Idieresis", "Igrave",
+ /* 0x0D0 */
+ "Oacute", "Ocircumflex", "apple", "Ograve",
+ "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
+ "circumflex", "tilde", "macron", "breve",
+ "dotaccent", "ring", "cedilla", "hungarumlaut",
+ /* 0x0E0 */
+ "ogonek", "caron", "Lslash", "lslash",
+ "Scaron", "scaron", "Zcaron", "zcaron",
+ "brokenbar", "Eth", "eth", "Yacute",
+ "yacute", "Thorn", "thorn", "minus",
+ /* 0x0F0 */
+ "multiply", "onesuperior", "twosuperior", "threesuperior",
+ "onehalf", "onequarter", "threequarters", "franc",
+ "Gbreve", "gbreve", "Idotaccent", "Scedilla",
+ "scedilla", "Cacute", "cacute", "Ccaron",
+ /* 0x100 */
+ "ccaron", "dcroat"
+ };
+
+ /** The FontFileReader used to read this TrueType font. */
+ protected FontFileReader fontFile;
+
+ /** Set to true to get even more debug output than with level DEBUG */
+ public static final boolean TRACE_ENABLED = false;
+
+ private static final String ENCODING = "WinAnsiEncoding"; // Default encoding
+
+ private static final short FIRST_CHAR = 0;
+
+ protected boolean useKerning = false;
+ private boolean isEmbeddable = true;
+ private boolean hasSerifs = true;
+ /**
+ * Table directory
+ */
+ protected Map dirTabs;
+
+ private Map> kerningTab; // for CIDs
+ private Map> ansiKerningTab; // For winAnsiEncoding
+ private List cmaps;
+ protected List unicodeMappings;
+
+ private int upem; // unitsPerEm from "head" table
+ private int nhmtx; // Number of horizontal metrics
+ private PostScriptVersion postScriptVersion;
+ protected int locaFormat;
+ /**
+ * Offset to last loca
+ */
+ protected long lastLoca = 0;
+ protected int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table)
+
+ /**
+ * Contains glyph data
+ */
+ protected OFMtxEntry[] mtxTab; // Contains glyph data
+
+ protected String postScriptName = "";
+ protected String fullName = "";
+ protected String notice = "";
+ protected final Set familyNames = new HashSet();
+ protected String subFamilyName = "";
+
+ private long italicAngle = 0;
+ private long isFixedPitch = 0;
+ private int fontBBox1 = 0;
+ private int fontBBox2 = 0;
+ private int fontBBox3 = 0;
+ private int fontBBox4 = 0;
+ private int capHeight = 0;
+ private int os2CapHeight = 0;
+ private int xHeight = 0;
+ private int os2xHeight = 0;
+ //Effective ascender/descender
+ private int ascender = 0;
+ private int descender = 0;
+ //Ascender/descender from hhea table
+ private int hheaAscender = 0;
+ private int hheaDescender = 0;
+ //Ascender/descender from OS/2 table
+ private int os2Ascender = 0;
+ private int os2Descender = 0;
+ private int usWeightClass = 0;
+
+ private short lastChar = 0;
+
+ private int[] ansiWidth;
+ private Map> ansiIndex;
+
+ // internal mapping of glyph indexes to unicode indexes
+ // used for quick mappings in this class
+ private final Map glyphToUnicodeMap = new HashMap();
+ private final Map unicodeToGlyphMap = new HashMap();
+
+ private boolean isCFF;
+
+ // advanced typographic table support
+ protected boolean useAdvanced = false;
+ protected OTFAdvancedTypographicTableReader advancedTableReader;
+
+ /**
+ * Version of the PostScript table (post
) contained in this font.
+ */
+ public static enum PostScriptVersion {
+ /** PostScript table version 1.0. */
+ V1,
+ /** PostScript table version 2.0. */
+ V2,
+ /** PostScript table version 3.0. */
+ V3,
+ /** Unknown version of the PostScript table. */
+ UNKNOWN;
+ }
+
+ /**
+ * logging instance
+ */
+ protected Log log = LogFactory.getLog(TTFFile.class);
+
+ public OpenFont() {
+ this(true, false);
+ }
+
+ /**
+ * Constructor
+ * @param useKerning true if kerning data should be loaded
+ * @param useAdvanced true if advanced typographic tables should be loaded
+ */
+ public OpenFont(boolean useKerning, boolean useAdvanced) {
+ this.useKerning = useKerning;
+ this.useAdvanced = useAdvanced;
+ }
+
+ /**
+ * Key-value helper class.
+ */
+ final class UnicodeMapping implements Comparable {
+
+ private final int unicodeIndex;
+ private final int glyphIndex;
+
+ UnicodeMapping(int glyphIndex, int unicodeIndex) {
+ this.unicodeIndex = unicodeIndex;
+ this.glyphIndex = glyphIndex;
+ glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex));
+ unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex));
+ }
+
+ /**
+ * Returns the glyphIndex.
+ * @return the glyph index
+ */
+ public int getGlyphIndex() {
+ return glyphIndex;
+ }
+
+ /**
+ * Returns the unicodeIndex.
+ * @return the Unicode index
+ */
+ public int getUnicodeIndex() {
+ return unicodeIndex;
+ }
+
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = unicodeIndex;
+ hc = 19 * hc + (hc ^ glyphIndex);
+ return hc;
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals(Object o) {
+ if (o instanceof UnicodeMapping) {
+ UnicodeMapping m = (UnicodeMapping) o;
+ if (unicodeIndex != m.unicodeIndex) {
+ return false;
+ } else {
+ return (glyphIndex == m.glyphIndex);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo(Object o) {
+ if (o instanceof UnicodeMapping) {
+ UnicodeMapping m = (UnicodeMapping) o;
+ if (unicodeIndex > m.unicodeIndex) {
+ return 1;
+ } else if (unicodeIndex < m.unicodeIndex) {
+ return -1;
+ } else {
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * Obtain directory table entry.
+ * @param name (tag) of entry
+ * @return a directory table entry or null if none found
+ */
+ public OFDirTabEntry getDirectoryEntry(OFTableName name) {
+ return dirTabs.get(name);
+ }
+
+ /**
+ * Position inputstream to position indicated
+ * in the dirtab offset + offset
+ * @param in font file reader
+ * @param tableName (tag) of table
+ * @param offset from start of table
+ * @return true if seek succeeded
+ * @throws IOException if I/O exception occurs during seek
+ */
+ public boolean seekTab(FontFileReader in, OFTableName tableName,
+ long offset) throws IOException {
+ OFDirTabEntry dt = dirTabs.get(tableName);
+ if (dt == null) {
+ log.error("Dirtab " + tableName.getName() + " not found.");
+ return false;
+ } else {
+ in.seekSet(dt.getOffset() + offset);
+ }
+ return true;
+ }
+
+ /**
+ * Convert from truetype unit to pdf unit based on the
+ * unitsPerEm field in the "head" table
+ * @param n truetype unit
+ * @return pdf unit
+ */
+ public int convertTTFUnit2PDFUnit(int n) {
+ int ret;
+ if (n < 0) {
+ long rest1 = n % upem;
+ long storrest = 1000 * rest1;
+ long ledd2 = (storrest != 0 ? rest1 / storrest : 0);
+ ret = -((-1000 * n) / upem - (int)ledd2);
+ } else {
+ ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Read the cmap table,
+ * return false if the table is not present or only unsupported
+ * tables are present. Currently only unicode cmaps are supported.
+ * Set the unicodeIndex in the TTFMtxEntries and fills in the
+ * cmaps vector.
+ */
+ protected boolean readCMAP() throws IOException {
+
+ unicodeMappings = new ArrayList();
+
+ seekTab(fontFile, OFTableName.CMAP, 2);
+ int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables
+ long cmapUniOffset = 0;
+ long symbolMapOffset = 0;
+
+ if (log.isDebugEnabled()) {
+ log.debug(numCMap + " cmap tables");
+ }
+
+ //Read offset for all tables. We are only interested in the unicode table
+ for (int i = 0; i < numCMap; i++) {
+ int cmapPID = fontFile.readTTFUShort();
+ int cmapEID = fontFile.readTTFUShort();
+ long cmapOffset = fontFile.readTTFLong();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID);
+ }
+
+ if (cmapPID == 3 && cmapEID == 1) {
+ cmapUniOffset = cmapOffset;
+ }
+ if (cmapPID == 3 && cmapEID == 0) {
+ symbolMapOffset = cmapOffset;
+ }
+ }
+
+ if (cmapUniOffset > 0) {
+ return readUnicodeCmap(cmapUniOffset, 1);
+ } else if (symbolMapOffset > 0) {
+ return readUnicodeCmap(symbolMapOffset, 0);
+ } else {
+ log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table"
+ + " not present. Aborting");
+ return false;
+ }
+ }
+
+ private boolean readUnicodeCmap(long cmapUniOffset, int encodingID)
+ throws IOException {
+ //Read CMAP table and correct mtxTab.index
+ int mtxPtr = 0;
+
+ // Read unicode cmap
+ seekTab(fontFile, OFTableName.CMAP, cmapUniOffset);
+ int cmapFormat = fontFile.readTTFUShort();
+ /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length
+
+ if (log.isDebugEnabled()) {
+ log.debug("CMAP format: " + cmapFormat);
+ }
+
+ if (cmapFormat == 4) {
+ fontFile.skip(2); // Skip version number
+ int cmapSegCountX2 = fontFile.readTTFUShort();
+ int cmapSearchRange = fontFile.readTTFUShort();
+ int cmapEntrySelector = fontFile.readTTFUShort();
+ int cmapRangeShift = fontFile.readTTFUShort();
+
+ if (log.isDebugEnabled()) {
+ log.debug("segCountX2 : " + cmapSegCountX2);
+ log.debug("searchRange : " + cmapSearchRange);
+ log.debug("entrySelector: " + cmapEntrySelector);
+ log.debug("rangeShift : " + cmapRangeShift);
+ }
+
+
+ int[] cmapEndCounts = new int[cmapSegCountX2 / 2];
+ int[] cmapStartCounts = new int[cmapSegCountX2 / 2];
+ int[] cmapDeltas = new int[cmapSegCountX2 / 2];
+ int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2];
+
+ for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+ cmapEndCounts[i] = fontFile.readTTFUShort();
+ }
+
+ fontFile.skip(2); // Skip reservedPad
+
+ for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+ cmapStartCounts[i] = fontFile.readTTFUShort();
+ }
+
+ for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+ cmapDeltas[i] = fontFile.readTTFShort();
+ }
+
+ //int startRangeOffset = in.getCurrentPos();
+
+ for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+ cmapRangeOffsets[i] = fontFile.readTTFUShort();
+ }
+
+ int glyphIdArrayOffset = fontFile.getCurrentPos();
+
+ BitSet eightBitGlyphs = new BitSet(256);
+
+ // Insert the unicode id for the glyphs in mtxTab
+ // and fill in the cmaps ArrayList
+ for (int i = 0; i < cmapStartCounts.length; i++) {
+
+ if (log.isTraceEnabled()) {
+ log.trace(i + ": " + cmapStartCounts[i]
+ + " - " + cmapEndCounts[i]);
+ }
+ if (log.isDebugEnabled()) {
+ if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) {
+ log.debug("Font contains glyphs in the Unicode private use area: "
+ + Integer.toHexString(cmapStartCounts[i]) + " - "
+ + Integer.toHexString(cmapEndCounts[i]));
+ }
+ }
+
+ for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) {
+
+ // Update lastChar
+ if (j < 256 && j > lastChar) {
+ lastChar = (short)j;
+ }
+
+ if (j < 256) {
+ eightBitGlyphs.set(j);
+ }
+
+ if (mtxPtr < mtxTab.length) {
+ int glyphIdx;
+ // the last character 65535 = .notdef
+ // may have a range offset
+ if (cmapRangeOffsets[i] != 0 && j != 65535) {
+ int glyphOffset = glyphIdArrayOffset
+ + ((cmapRangeOffsets[i] / 2)
+ + (j - cmapStartCounts[i])
+ + (i)
+ - cmapSegCountX2 / 2) * 2;
+ fontFile.seekSet(glyphOffset);
+ glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i])
+ & 0xffff;
+ //mtxTab[glyphIdx].setName(mtxTab[glyphIdx].getName() + " - "+(char)j);
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
+ mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
+
+ if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) {
+ //Experimental: Mapping 0xF020-0xF0FF to 0x0020-0x00FF
+ //Tested with Wingdings and Symbol TTF fonts which map their
+ //glyphs in the region 0xF020-0xF0FF.
+ int mapped = j - 0xF000;
+ if (!eightBitGlyphs.get(mapped)) {
+ //Only map if Unicode code point hasn't been mapped before
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, mapped));
+ mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped));
+ }
+ }
+
+ // Also add winAnsiWidth
+ List v = ansiIndex.get(new Integer(j));
+ if (v != null) {
+ for (Integer aIdx : v) {
+ ansiWidth[aIdx.intValue()]
+ = mtxTab[glyphIdx].getWx();
+
+ if (log.isTraceEnabled()) {
+ log.trace("Added width "
+ + mtxTab[glyphIdx].getWx()
+ + " uni: " + j
+ + " ansi: " + aIdx.intValue());
+ }
+ }
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("Idx: "
+ + glyphIdx
+ + " Delta: " + cmapDeltas[i]
+ + " Unicode: " + j
+ + " name: " + mtxTab[glyphIdx].getName());
+ }
+ } else {
+ glyphIdx = (j + cmapDeltas[i]) & 0xffff;
+
+ if (glyphIdx < mtxTab.length) {
+ mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
+ } else {
+ log.debug("Glyph " + glyphIdx
+ + " out of range: "
+ + mtxTab.length);
+ }
+
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
+ if (glyphIdx < mtxTab.length) {
+ mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
+ } else {
+ log.debug("Glyph " + glyphIdx
+ + " out of range: "
+ + mtxTab.length);
+ }
+
+ // Also add winAnsiWidth
+ List v = ansiIndex.get(new Integer(j));
+ if (v != null) {
+ for (Integer aIdx : v) {
+ ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx();
+ }
+ }
+
+ //getLogger().debug("IIdx: " +
+ // mtxPtr +
+ // " Delta: " + cmap_deltas[i] +
+ // " Unicode: " + j +
+ // " name: " +
+ // mtxTab[(j+cmap_deltas[i]) & 0xffff].name);
+
+ }
+ if (glyphIdx < mtxTab.length) {
+ if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) {
+ mtxPtr++;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ log.error("Cmap format not supported: " + cmapFormat);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isInPrivateUseArea(int start, int end) {
+ return (isInPrivateUseArea(start) || isInPrivateUseArea(end));
+ }
+
+ private boolean isInPrivateUseArea(int unicode) {
+ return (unicode >= 0xE000 && unicode <= 0xF8FF);
+ }
+
+ /**
+ *
+ * @return mmtx data
+ */
+ public List getMtx() {
+ return Collections.unmodifiableList(Arrays.asList(mtxTab));
+ }
+
+ /**
+ * Print first char/last char
+ */
+ private void printMaxMin() {
+ int min = 255;
+ int max = 0;
+ for (int i = 0; i < mtxTab.length; i++) {
+ if (mtxTab[i].getIndex() < min) {
+ min = mtxTab[i].getIndex();
+ }
+ if (mtxTab[i].getIndex() > max) {
+ max = mtxTab[i].getIndex();
+ }
+ }
+ log.info("Min: " + min);
+ log.info("Max: " + max);
+ }
+
+
+ /**
+ * Reads the font using a FontFileReader.
+ *
+ * @param in The FontFileReader to use
+ * @throws IOException In case of an I/O problem
+ */
+ public void readFont(FontFileReader in, String header) throws IOException {
+ readFont(in, header, (String)null);
+ }
+
+ /**
+ * initialize the ansiWidths array (for winAnsiEncoding)
+ * and fill with the missingwidth
+ */
+ protected void initAnsiWidths() {
+ ansiWidth = new int[256];
+ for (int i = 0; i < 256; i++) {
+ ansiWidth[i] = mtxTab[0].getWx();
+ }
+
+ // Create an index hash to the ansiWidth
+ // Can't just index the winAnsiEncoding when inserting widths
+ // same char (eg bullet) is repeated more than one place
+ ansiIndex = new HashMap>();
+ for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
+ Integer ansi = new Integer(i);
+ Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]);
+
+ List v = ansiIndex.get(uni);
+ if (v == null) {
+ v = new ArrayList();
+ ansiIndex.put(uni, v);
+ }
+ v.add(ansi);
+ }
+ }
+
+ /**
+ * Read the font data.
+ * If the fontfile is a TrueType Collection (.ttc file)
+ * the name of the font to read data for must be supplied,
+ * else the name is ignored.
+ *
+ * @param in The FontFileReader to use
+ * @param name The name of the font
+ * @return boolean Returns true if the font is valid
+ * @throws IOException In case of an I/O problem
+ */
+ public boolean readFont(FontFileReader in, String header, String name) throws IOException {
+ initializeFont(in);
+ /*
+ * Check if TrueType collection, and that the name
+ * exists in the collection
+ */
+ if (!checkTTC(header, name)) {
+ if (name == null) {
+ throw new IllegalArgumentException(
+ "For TrueType collection you must specify which font "
+ + "to select (-ttcname)");
+ } else {
+ throw new IOException(
+ "Name does not exist in the TrueType collection: " + name);
+ }
+ }
+
+ readDirTabs();
+ readFontHeader();
+ getNumGlyphs();
+ if (log.isDebugEnabled()) {
+ log.debug("Number of glyphs in font: " + numberOfGlyphs);
+ }
+ readHorizontalHeader();
+ readHorizontalMetrics();
+ initAnsiWidths();
+ readPostScript();
+ readOS2();
+ determineAscDesc();
+
+ readName();
+ boolean pcltFound = readPCLT();
+ // Read cmap table and fill in ansiwidths
+ boolean valid = readCMAP();
+ if (!valid) {
+ return false;
+ }
+
+ // Create cmaps for bfentries
+ createCMaps();
+ updateBBoxAndOffset();
+
+ if (useKerning) {
+ readKerning();
+ }
+ handleCharacterSpacing(in);
+
+ guessVerticalMetricsFromGlyphBBox();
+ return true;
+ }
+
+ /**
+ * Reads a font.
+ *
+ * @param in FontFileReader to read from
+ * @param name Name to be checked for in the font file
+ * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
+ * new index as (Integer) value)
+ * @throws IOException in case of an I/O problem
+ */
+ public void readFont(FontFileReader in, String header, MultiByteFont mbfont) throws IOException {
+ readFont(in, header, mbfont.getTTCName());
+ }
+
+ protected abstract void updateBBoxAndOffset() throws IOException;
+
+ protected abstract void readName() throws IOException;
+
+ protected abstract void initializeFont(FontFileReader in) throws IOException;
+
+ protected void handleCharacterSpacing(FontFileReader in) throws IOException {
+ // Read advanced typographic tables.
+ if (useAdvanced) {
+ try {
+ OTFAdvancedTypographicTableReader atr
+ = new OTFAdvancedTypographicTableReader(this, in);
+ atr.readAll();
+ this.advancedTableReader = atr;
+ } catch (AdvancedTypographicTableFormatException e) {
+ log.warn(
+ "Encountered format constraint violation in advanced (typographic) table (AT) "
+ + "in font '" + getFullName() + "', ignoring AT data: "
+ + e.getMessage()
+ );
+ }
+ }
+
+ }
+
+ protected void createCMaps() {
+ cmaps = new ArrayList();
+ int unicodeStart;
+ int glyphStart;
+ int unicodeEnd;
+
+ Iterator e = unicodeMappings.iterator();
+ UnicodeMapping um = e.next();
+ UnicodeMapping lastMapping = um;
+
+ unicodeStart = um.getUnicodeIndex();
+ glyphStart = um.getGlyphIndex();
+
+ while (e.hasNext()) {
+ um = e.next();
+ if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex())
+ || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) {
+ unicodeEnd = lastMapping.getUnicodeIndex();
+ cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart));
+ unicodeStart = um.getUnicodeIndex();
+ glyphStart = um.getGlyphIndex();
+ }
+ lastMapping = um;
+ }
+
+ unicodeEnd = lastMapping.getUnicodeIndex();
+ cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart));
+ }
+
+ /**
+ * Returns the PostScript name of the font.
+ * @return String The PostScript name
+ */
+ public String getPostScriptName() {
+ if (postScriptName.length() == 0) {
+ return FontUtil.stripWhiteSpace(getFullName());
+ } else {
+ return postScriptName;
+ }
+ }
+
+ PostScriptVersion getPostScriptVersion() {
+ return postScriptVersion;
+ }
+
+ /**
+ * Returns the font family names of the font.
+ * @return Set The family names (a Set of Strings)
+ */
+ public Set getFamilyNames() {
+ return familyNames;
+ }
+
+ /**
+ * Returns the font sub family name of the font.
+ * @return String The sub family name
+ */
+ public String getSubFamilyName() {
+ return subFamilyName;
+ }
+
+ /**
+ * Returns the full name of the font.
+ * @return String The full name
+ */
+ public String getFullName() {
+ return fullName;
+ }
+
+ /**
+ * Returns the name of the character set used.
+ * @return String The caracter set
+ */
+ public String getCharSetName() {
+ return ENCODING;
+ }
+
+ /**
+ * Returns the CapHeight attribute of the font.
+ * @return int The CapHeight
+ */
+ public int getCapHeight() {
+ return convertTTFUnit2PDFUnit(capHeight);
+ }
+
+ /**
+ * Returns the XHeight attribute of the font.
+ * @return int The XHeight
+ */
+ public int getXHeight() {
+ return convertTTFUnit2PDFUnit(xHeight);
+ }
+
+ /**
+ * Returns the number of bytes necessary to pad the currentPosition so that a table begins
+ * on a 4-byte boundary.
+ * @param currentPosition the position to pad.
+ * @return int the number of bytes to pad.
+ */
+ protected int getPadSize(int currentPosition) {
+ int padSize = 4 - (currentPosition % 4);
+ return padSize < 4 ? padSize : 0;
+ }
+
+ /**
+ * Returns the Flags attribute of the font.
+ * @return int The Flags
+ */
+ public int getFlags() {
+ int flags = 32; // Use Adobe Standard charset
+ if (italicAngle != 0) {
+ flags |= 64;
+ }
+ if (isFixedPitch != 0) {
+ flags |= 2;
+ }
+ if (hasSerifs) {
+ flags |= 1;
+ }
+ return flags;
+ }
+
+ /**
+ * Returns the weight class of this font. Valid values are 100, 200....,800, 900.
+ * @return the weight class value (or 0 if there was no OS/2 table in the font)
+ */
+ public int getWeightClass() {
+ return this.usWeightClass;
+ }
+
+ /**
+ * Returns the StemV attribute of the font.
+ * @return String The StemV
+ */
+ public String getStemV() {
+ return "0";
+ }
+
+ /**
+ * Returns the ItalicAngle attribute of the font.
+ * @return String The ItalicAngle
+ */
+ public String getItalicAngle() {
+ String ia = Short.toString((short)(italicAngle / 0x10000));
+
+ // This is the correct italic angle, however only int italic
+ // angles are supported at the moment so this is commented out.
+ /*
+ * if ((italicAngle % 0x10000) > 0 )
+ * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000)));
+ */
+ return ia;
+ }
+
+ /**
+ * @return int[] The font bbox
+ */
+ public int[] getFontBBox() {
+ final int[] fbb = new int[4];
+ fbb[0] = convertTTFUnit2PDFUnit(fontBBox1);
+ fbb[1] = convertTTFUnit2PDFUnit(fontBBox2);
+ fbb[2] = convertTTFUnit2PDFUnit(fontBBox3);
+ fbb[3] = convertTTFUnit2PDFUnit(fontBBox4);
+
+ return fbb;
+ }
+
+ /**
+ * Returns the LowerCaseAscent attribute of the font.
+ * @return int The LowerCaseAscent
+ */
+ public int getLowerCaseAscent() {
+ return convertTTFUnit2PDFUnit(ascender);
+ }
+
+ /**
+ * Returns the LowerCaseDescent attribute of the font.
+ * @return int The LowerCaseDescent
+ */
+ public int getLowerCaseDescent() {
+ return convertTTFUnit2PDFUnit(descender);
+ }
+
+ /**
+ * Returns the index of the last character, but this is for WinAnsiEncoding
+ * only, so the last char is < 256.
+ * @return short Index of the last character (<256)
+ */
+ public short getLastChar() {
+ return lastChar;
+ }
+
+ /**
+ * Returns the index of the first character.
+ * @return short Index of the first character
+ */
+ public short getFirstChar() {
+ return FIRST_CHAR;
+ }
+
+ /**
+ * Returns an array of character widths.
+ * @return int[] The character widths
+ */
+ public int[] getWidths() {
+ int[] wx = new int[mtxTab.length];
+ for (int i = 0; i < wx.length; i++) {
+ wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx());
+ }
+
+ return wx;
+ }
+
+ /**
+ * Returns an array (xMin, yMin, xMax, yMax) for a glyph.
+ *
+ * @param glyphIndex the index of the glyph
+ * @return int[] Array defining bounding box.
+ */
+ public int[] getBBox(int glyphIndex) {
+ int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox();
+ int[] bbox = new int[4];
+ for (int i = 0; i < 4; i++) {
+ bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]);
+ }
+ return bbox;
+ }
+
+ /**
+ * Returns the width of a given character.
+ * @param idx Index of the character
+ * @return int Standard width
+ */
+ public int getCharWidth(int idx) {
+ return convertTTFUnit2PDFUnit(ansiWidth[idx]);
+ }
+
+ /**
+ * Returns the kerning table.
+ * @return Map The kerning table
+ */
+ public Map> getKerning() {
+ return kerningTab;
+ }
+
+ /**
+ * Returns the ANSI kerning table.
+ * @return Map The ANSI kerning table
+ */
+ public Map> getAnsiKerning() {
+ return ansiKerningTab;
+ }
+
+ /**
+ * Indicates if the font may be embedded.
+ * @return boolean True if it may be embedded
+ */
+ public boolean isEmbeddable() {
+ return isEmbeddable;
+ }
+
+ /**
+ * Indicates whether or not the font is an OpenType
+ * CFF font (rather than a TrueType font).
+ * @return true if the font is in OpenType CFF format.
+ */
+ public boolean isCFF() {
+ return this.isCFF;
+ }
+
+ /**
+ * Read Table Directory from the current position in the
+ * FontFileReader and fill the global HashMap dirTabs
+ * with the table name (String) as key and a TTFDirTabEntry
+ * as value.
+ * @throws IOException in case of an I/O problem
+ */
+ protected void readDirTabs() throws IOException {
+ int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes)
+ switch (sfntVersion) {
+ case 0x10000:
+ log.debug("sfnt version: OpenType 1.0");
+ break;
+ case 0x4F54544F: //"OTTO"
+ this.isCFF = true;
+ log.debug("sfnt version: OpenType with CFF data");
+ break;
+ case 0x74727565: //"true"
+ log.debug("sfnt version: Apple TrueType");
+ break;
+ case 0x74797031: //"typ1"
+ log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper");
+ break;
+ default:
+ log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion));
+ break;
+ }
+ int ntabs = fontFile.readTTFUShort();
+ fontFile.skip(6); // 3xTTF_USHORT_SIZE
+
+ dirTabs = new HashMap();
+ OFDirTabEntry[] pd = new OFDirTabEntry[ntabs];
+ log.debug("Reading " + ntabs + " dir tables");
+
+ for (int i = 0; i < ntabs; i++) {
+ pd[i] = new OFDirTabEntry();
+ String tableName = pd[i].read(fontFile);
+ dirTabs.put(OFTableName.getValue(tableName), pd[i]);
+ }
+ dirTabs.put(OFTableName.TABLE_DIRECTORY,
+ new OFDirTabEntry(0L, fontFile.getCurrentPos()));
+ log.debug("dir tables: " + dirTabs.keySet());
+ }
+
+ /**
+ * Read the "head" table, this reads the bounding box and
+ * sets the upem (unitsPerEM) variable
+ * @throws IOException in case of an I/O problem
+ */
+ protected void readFontHeader() throws IOException {
+ seekTab(fontFile, OFTableName.HEAD, 2 * 4 + 2 * 4);
+ int flags = fontFile.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug("flags: " + flags + " - " + Integer.toString(flags, 2));
+ }
+ upem = fontFile.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug("unit per em: " + upem);
+ }
+
+ fontFile.skip(16);
+
+ fontBBox1 = fontFile.readTTFShort();
+ fontBBox2 = fontFile.readTTFShort();
+ fontBBox3 = fontFile.readTTFShort();
+ fontBBox4 = fontFile.readTTFShort();
+ if (log.isDebugEnabled()) {
+ log.debug("font bbox: xMin=" + fontBBox1
+ + " yMin=" + fontBBox2
+ + " xMax=" + fontBBox3
+ + " yMax=" + fontBBox4);
+ }
+
+ fontFile.skip(2 + 2 + 2);
+
+ locaFormat = fontFile.readTTFShort();
+ }
+
+ /**
+ * Read the number of glyphs from the "maxp" table
+ * @throws IOException in case of an I/O problem
+ */
+ protected void getNumGlyphs() throws IOException {
+ seekTab(fontFile, OFTableName.MAXP, 4);
+ numberOfGlyphs = fontFile.readTTFUShort();
+ }
+
+
+ /**
+ * Read the "hhea" table to find the ascender and descender and
+ * size of "hmtx" table, as a fixed size font might have only
+ * one width.
+ * @throws IOException in case of an I/O problem
+ */
+ protected void readHorizontalHeader()
+ throws IOException {
+ seekTab(fontFile, OFTableName.HHEA, 4);
+ hheaAscender = fontFile.readTTFShort();
+ hheaDescender = fontFile.readTTFShort();
+
+ fontFile.skip(2 + 2 + 3 * 2 + 8 * 2);
+ nhmtx = fontFile.readTTFUShort();
+
+ if (log.isDebugEnabled()) {
+ log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender));
+ log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender));
+ log.debug("Number of horizontal metrics: " + nhmtx);
+ }
+ }
+
+ /**
+ * Read "hmtx" table and put the horizontal metrics
+ * in the mtxTab array. If the number of metrics is less
+ * than the number of glyphs (eg fixed size fonts), extend
+ * the mtxTab array and fill in the missing widths
+ * @throws IOException in case of an I/O problem
+ */
+ protected void readHorizontalMetrics()
+ throws IOException {
+ seekTab(fontFile, OFTableName.HMTX, 0);
+
+ int mtxSize = Math.max(numberOfGlyphs, nhmtx);
+ mtxTab = new OFMtxEntry[mtxSize];
+
+ if (log.isTraceEnabled()) {
+ log.trace("*** Widths array: \n");
+ }
+ for (int i = 0; i < mtxSize; i++) {
+ mtxTab[i] = new OFMtxEntry();
+ }
+ for (int i = 0; i < nhmtx; i++) {
+ mtxTab[i].setWx(fontFile.readTTFUShort());
+ mtxTab[i].setLsb(fontFile.readTTFUShort());
+
+ if (log.isTraceEnabled()) {
+ log.trace(" width[" + i + "] = "
+ + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
+ }
+ }
+
+ if (nhmtx < mtxSize) {
+ // Fill in the missing widths
+ int lastWidth = mtxTab[nhmtx - 1].getWx();
+ for (int i = nhmtx; i < mtxSize; i++) {
+ mtxTab[i].setWx(lastWidth);
+ mtxTab[i].setLsb(fontFile.readTTFUShort());
+ }
+ }
+ }
+
+
+ /**
+ * Read the "post" table
+ * containing the PostScript names of the glyphs.
+ */
+ protected void readPostScript() throws IOException {
+ seekTab(fontFile, OFTableName.POST, 0);
+ int postFormat = fontFile.readTTFLong();
+ italicAngle = fontFile.readTTFULong();
+ //underlinePosition
+ fontFile.readTTFShort();
+ //underlineThickness
+ fontFile.readTTFShort();
+ isFixedPitch = fontFile.readTTFULong();
+
+ //Skip memory usage values
+ fontFile.skip(4 * 4);
+
+ log.debug("PostScript format: 0x" + Integer.toHexString(postFormat));
+ switch (postFormat) {
+ case 0x00010000:
+ log.debug("PostScript format 1");
+ postScriptVersion = PostScriptVersion.V1;
+ for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) {
+ mtxTab[i].setName(MAC_GLYPH_ORDERING[i]);
+ }
+ break;
+ case 0x00020000:
+ log.debug("PostScript format 2");
+ postScriptVersion = PostScriptVersion.V2;
+ int numGlyphStrings = 0;
+
+ // Read Number of Glyphs
+ int l = fontFile.readTTFUShort();
+
+ // Read indexes
+ for (int i = 0; i < l; i++) {
+ mtxTab[i].setIndex(fontFile.readTTFUShort());
+
+ if (mtxTab[i].getIndex() > 257) {
+ //Index is not in the Macintosh standard set
+ numGlyphStrings++;
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("PostScript index: " + mtxTab[i].getIndexAsString());
+ }
+ }
+
+ // firstChar=minIndex;
+ String[] psGlyphsBuffer = new String[numGlyphStrings];
+ if (log.isDebugEnabled()) {
+ log.debug("Reading " + numGlyphStrings
+ + " glyphnames, that are not in the standard Macintosh"
+ + " set. Total number of glyphs=" + l);
+ }
+ for (int i = 0; i < psGlyphsBuffer.length; i++) {
+ psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte());
+ }
+
+ //Set glyph names
+ for (int i = 0; i < l; i++) {
+ if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) {
+ mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]);
+ } else {
+ if (!mtxTab[i].isIndexReserved()) {
+ int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length;
+
+ if (log.isTraceEnabled()) {
+ log.trace(k + " i=" + i + " mtx=" + mtxTab.length
+ + " ps=" + psGlyphsBuffer.length);
+ }
+
+ mtxTab[i].setName(psGlyphsBuffer[k]);
+ }
+ }
+ }
+
+ break;
+ case 0x00030000:
+ // PostScript format 3 contains no glyph names
+ log.debug("PostScript format 3");
+ postScriptVersion = PostScriptVersion.V3;
+ break;
+ default:
+ log.error("Unknown PostScript format: " + postFormat);
+ postScriptVersion = PostScriptVersion.UNKNOWN;
+ }
+ }
+
+
+ /**
+ * Read the "OS/2" table
+ */
+ protected void readOS2() throws IOException {
+ // Check if font is embeddable
+ OFDirTabEntry os2Entry = dirTabs.get(OFTableName.OS2);
+ if (os2Entry != null) {
+ seekTab(fontFile, OFTableName.OS2, 0);
+ int version = fontFile.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug("OS/2 table: version=" + version
+ + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength());
+ }
+ fontFile.skip(2); //xAvgCharWidth
+ this.usWeightClass = fontFile.readTTFUShort();
+
+ // usWidthClass
+ fontFile.skip(2);
+
+ int fsType = fontFile.readTTFUShort();
+ if (fsType == 2) {
+ isEmbeddable = false;
+ } else {
+ isEmbeddable = true;
+ }
+ fontFile.skip(11 * 2);
+ fontFile.skip(10); //panose array
+ fontFile.skip(4 * 4); //unicode ranges
+ fontFile.skip(4);
+ fontFile.skip(3 * 2);
+ int v;
+ os2Ascender = fontFile.readTTFShort(); //sTypoAscender
+ os2Descender = fontFile.readTTFShort(); //sTypoDescender
+ if (log.isDebugEnabled()) {
+ log.debug("sTypoAscender: " + os2Ascender
+ + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender));
+ log.debug("sTypoDescender: " + os2Descender
+ + " -> internal " + convertTTFUnit2PDFUnit(os2Descender));
+ }
+ v = fontFile.readTTFShort(); //sTypoLineGap
+ if (log.isDebugEnabled()) {
+ log.debug("sTypoLineGap: " + v);
+ }
+ v = fontFile.readTTFUShort(); //usWinAscent
+ if (log.isDebugEnabled()) {
+ log.debug("usWinAscent: " + formatUnitsForDebug(v));
+ }
+ v = fontFile.readTTFUShort(); //usWinDescent
+ if (log.isDebugEnabled()) {
+ log.debug("usWinDescent: " + formatUnitsForDebug(v));
+ }
+
+ //version 1 OS/2 table might end here
+ if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) {
+ fontFile.skip(2 * 4);
+ this.os2xHeight = fontFile.readTTFShort(); //sxHeight
+ this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight
+ if (log.isDebugEnabled()) {
+ log.debug("sxHeight: " + this.os2xHeight);
+ log.debug("sCapHeight: " + this.os2CapHeight);
+ }
+ }
+
+ } else {
+ isEmbeddable = true;
+ }
+ }
+
+ /**
+ * Read the "PCLT" table to find xHeight and capHeight.
+ * @throws IOException In case of a I/O problem
+ */
+ protected boolean readPCLT() throws IOException {
+ OFDirTabEntry dirTab = dirTabs.get(OFTableName.PCLT);
+ if (dirTab != null) {
+ fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2);
+ xHeight = fontFile.readTTFUShort();
+ log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight));
+ fontFile.skip(2 * 2);
+ capHeight = fontFile.readTTFUShort();
+ log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight));
+ fontFile.skip(2 + 16 + 8 + 6 + 1 + 1);
+
+ int serifStyle = fontFile.readTTFUByte();
+ serifStyle = serifStyle >> 6;
+ serifStyle = serifStyle & 3;
+ if (serifStyle == 1) {
+ hasSerifs = false;
+ } else {
+ hasSerifs = true;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determines the right source for the ascender and descender values. The problem here is
+ * that the interpretation of these values is not the same for every font. There doesn't seem
+ * to be a uniform definition of an ascender and a descender. In some fonts
+ * the hhea values are defined after the Apple interpretation, but not in every font. The
+ * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the
+ * baseline so we need values which add up more or less to the "em box". However, due to
+ * accent modifiers a character can grow beyond the em box.
+ */
+ protected void determineAscDesc() {
+ int hheaBoxHeight = hheaAscender - hheaDescender;
+ int os2BoxHeight = os2Ascender - os2Descender;
+ if (os2Ascender > 0 && os2BoxHeight <= upem) {
+ ascender = os2Ascender;
+ descender = os2Descender;
+ } else if (hheaAscender > 0 && hheaBoxHeight <= upem) {
+ ascender = hheaAscender;
+ descender = hheaDescender;
+ } else {
+ if (os2Ascender > 0) {
+ //Fall back to info from OS/2 if possible
+ ascender = os2Ascender;
+ descender = os2Descender;
+ } else {
+ ascender = hheaAscender;
+ descender = hheaDescender;
+ }
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Font box height: " + (ascender - descender));
+ if (ascender - descender > upem) {
+ log.debug("Ascender and descender together are larger than the em box.");
+ }
+ }
+ }
+
+ protected void guessVerticalMetricsFromGlyphBBox() {
+ // Approximate capHeight from height of "H"
+ // It's most unlikely that a font misses the PCLT table
+ // This also assumes that postscriptnames exists ("H")
+ // Should look it up in the cmap (that wouldn't help
+ // for charsets without H anyway...)
+ // Same for xHeight with the letter "x"
+ int localCapHeight = 0;
+ int localXHeight = 0;
+ int localAscender = 0;
+ int localDescender = 0;
+ for (int i = 0; i < mtxTab.length; i++) {
+ if ("H".equals(mtxTab[i].getName())) {
+ localCapHeight = mtxTab[i].getBoundingBox()[3];
+ } else if ("x".equals(mtxTab[i].getName())) {
+ localXHeight = mtxTab[i].getBoundingBox()[3];
+ } else if ("d".equals(mtxTab[i].getName())) {
+ localAscender = mtxTab[i].getBoundingBox()[3];
+ } else if ("p".equals(mtxTab[i].getName())) {
+ localDescender = mtxTab[i].getBoundingBox()[1];
+ } else {
+ // OpenType Fonts with a version 3.0 "post" table don't have glyph names.
+ // Use Unicode indices instead.
+ List unicodeIndex = mtxTab[i].getUnicodeIndex();
+ if (unicodeIndex.size() > 0) {
+ //Only the first index is used
+ char ch = (char)((Integer)unicodeIndex.get(0)).intValue();
+ if (ch == 'H') {
+ localCapHeight = mtxTab[i].getBoundingBox()[3];
+ } else if (ch == 'x') {
+ localXHeight = mtxTab[i].getBoundingBox()[3];
+ } else if (ch == 'd') {
+ localAscender = mtxTab[i].getBoundingBox()[3];
+ } else if (ch == 'p') {
+ localDescender = mtxTab[i].getBoundingBox()[1];
+ }
+ }
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender));
+ log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender));
+ }
+ if (ascender - descender > upem) {
+ log.debug("Replacing specified ascender/descender with derived values to get values"
+ + " which fit in the em box.");
+ ascender = localAscender;
+ descender = localDescender;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight));
+ log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight));
+ }
+ if (capHeight == 0) {
+ capHeight = localCapHeight;
+ if (capHeight == 0) {
+ capHeight = os2CapHeight;
+ }
+ if (capHeight == 0) {
+ log.debug("capHeight value could not be determined."
+ + " The font may not work as expected.");
+ }
+ }
+ if (xHeight == 0) {
+ xHeight = localXHeight;
+ if (xHeight == 0) {
+ xHeight = os2xHeight;
+ }
+ if (xHeight == 0) {
+ log.debug("xHeight value could not be determined."
+ + " The font may not work as expected.");
+ }
+ }
+ }
+
+ /**
+ * Read the kerning table, create a table for both CIDs and
+ * winAnsiEncoding.
+ * @throws IOException In case of a I/O problem
+ */
+ protected void readKerning() throws IOException {
+ // Read kerning
+ kerningTab = new HashMap>();
+ ansiKerningTab = new HashMap>();
+ OFDirTabEntry dirTab = dirTabs.get(OFTableName.KERN);
+ if (dirTab != null) {
+ seekTab(fontFile, OFTableName.KERN, 2);
+ for (int n = fontFile.readTTFUShort(); n > 0; n--) {
+ fontFile.skip(2 * 2);
+ int k = fontFile.readTTFUShort();
+ if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) {
+ return;
+ }
+ if ((k >> 8) != 0) {
+ continue;
+ }
+
+ k = fontFile.readTTFUShort();
+ fontFile.skip(3 * 2);
+ while (k-- > 0) {
+ int i = fontFile.readTTFUShort();
+ int j = fontFile.readTTFUShort();
+ int kpx = fontFile.readTTFShort();
+ if (kpx != 0) {
+ // CID kerning table entry, using unicode indexes
+ final Integer iObj = glyphToUnicode(i);
+ final Integer u2 = glyphToUnicode(j);
+ if (iObj == null) {
+ // happens for many fonts (Ubuntu font set),
+ // stray entries in the kerning table??
+ log.debug("Ignoring kerning pair because no Unicode index was"
+ + " found for the first glyph " + i);
+ } else if (u2 == null) {
+ log.debug("Ignoring kerning pair because Unicode index was"
+ + " found for the second glyph " + i);
+ } else {
+ Map adjTab = kerningTab.get(iObj);
+ if (adjTab == null) {
+ adjTab = new HashMap();
+ }
+ adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx)));
+ kerningTab.put(iObj, adjTab);
+ }
+ }
+ }
+ }
+
+ // Create winAnsiEncoded kerning table from kerningTab
+ // (could probably be simplified, for now we remap back to CID indexes and
+ // then to winAnsi)
+ for (Integer unicodeKey1 : kerningTab.keySet()) {
+ Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue());
+ Map akpx = new HashMap();
+ Map ckpx = kerningTab.get(unicodeKey1);
+
+ for (Integer unicodeKey2 : ckpx.keySet()) {
+ Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue());
+ Integer kern = ckpx.get(unicodeKey2);
+
+ Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator();
+ while (uniMap.hasNext()) {
+ Integer unicodeKey = (Integer)uniMap.next();
+ Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
+ for (int u = 0; u < ansiKeys.length; u++) {
+ akpx.put(ansiKeys[u], kern);
+ }
+ }
+ }
+
+ if (akpx.size() > 0) {
+ Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator();
+ while (uniMap.hasNext()) {
+ Integer unicodeKey = (Integer)uniMap.next();
+ Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
+ for (int u = 0; u < ansiKeys.length; u++) {
+ ansiKerningTab.put(ansiKeys[u], akpx);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Streams a font.
+ * @param ttfOut The interface for streaming TrueType tables.
+ * @exception IOException file write error
+ */
+ public void stream(TTFOutputStream ttfOut) throws IOException {
+ SortedSet> sortedDirTabs = sortDirTabMap(dirTabs);
+ byte[] file = fontFile.getAllBytes();
+ TTFTableOutputStream tableOut = ttfOut.getTableOutputStream();
+ TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream();
+ ttfOut.startFontStream();
+ for (Map.Entry entry : sortedDirTabs) {
+ int offset = (int) entry.getValue().getOffset();
+ int paddedLength = (int) entry.getValue().getLength();
+ paddedLength += getPadSize(offset + paddedLength);
+ if (entry.getKey().equals(OFTableName.GLYF)) {
+ streamGlyf(glyphOut, file, offset, paddedLength);
+ } else {
+ tableOut.streamTable(file, offset, paddedLength);
+ }
+ }
+ ttfOut.endFontStream();
+ }
+
+ private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset,
+ int tableLength) throws IOException {
+ //Stream all but the last glyph
+ int glyphStart = 0;
+ int glyphEnd = 0;
+ glyphOut.startGlyphStream();
+ for (int i = 0; i < mtxTab.length - 1; i++) {
+ glyphStart = (int) mtxTab[i].getOffset() + tableOffset;
+ glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset;
+ glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart);
+ }
+ glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd);
+ glyphOut.endGlyphStream();
+ }
+
+ /**
+ * Returns the order in which the tables in a TrueType font should be written to file.
+ * @param directoryTabs the map that is to be sorted.
+ * @return TTFTablesNames[] an array of table names sorted in the order they should appear in
+ * the TTF file.
+ */
+ SortedSet>
+ sortDirTabMap(Map directoryTabs) {
+ SortedSet> sortedSet
+ = new TreeSet>(
+ new Comparator>() {
+
+ public int compare(Entry o1,
+ Entry o2) {
+ return (int) (o1.getValue().getOffset() - o2.getValue().getOffset());
+ }
+ });
+ sortedSet.addAll(directoryTabs.entrySet());
+ return sortedSet;
+ }
+
+ /**
+ * Returns this font's character to glyph mapping.
+ *
+ * @return the font's cmap
+ */
+ public List getCMaps() {
+ return cmaps;
+ }
+
+ /**
+ * Check if this is a TrueType collection and that the given
+ * name exists in the collection.
+ * If it does, set offset in fontfile to the beginning of
+ * the Table Directory for that font.
+ * @param name The name to check
+ * @return True if not collection or font name present, false otherwise
+ * @throws IOException In case of an I/O problem
+ */
+ protected final boolean checkTTC(String tag, String name) throws IOException {
+ if ("ttcf".equals(tag)) {
+ // This is a TrueType Collection
+ fontFile.skip(4);
+
+ // Read directory offsets
+ int numDirectories = (int)fontFile.readTTFULong();
+ // int numDirectories=in.readTTFUShort();
+ long[] dirOffsets = new long[numDirectories];
+ for (int i = 0; i < numDirectories; i++) {
+ dirOffsets[i] = fontFile.readTTFULong();
+ }
+
+ log.info("This is a TrueType collection file with "
+ + numDirectories + " fonts");
+ log.info("Containing the following fonts: ");
+ // Read all the directories and name tables to check
+ // If the font exists - this is a bit ugly, but...
+ boolean found = false;
+
+ // Iterate through all name tables even if font
+ // Is found, just to show all the names
+ long dirTabOffset = 0;
+ for (int i = 0; (i < numDirectories); i++) {
+ fontFile.seekSet(dirOffsets[i]);
+ readDirTabs();
+
+ readName();
+
+ if (fullName.equals(name)) {
+ found = true;
+ dirTabOffset = dirOffsets[i];
+ log.info(fullName + " <-- selected");
+ } else {
+ log.info(fullName);
+ }
+
+ // Reset names
+ notice = "";
+ fullName = "";
+ familyNames.clear();
+ postScriptName = "";
+ subFamilyName = "";
+ }
+
+ fontFile.seekSet(dirTabOffset);
+ return found;
+ } else {
+ fontFile.seekSet(0);
+ return true;
+ }
+ }
+
+ /**
+ * Return TTC font names
+ * @param in FontFileReader to read from
+ * @return True if not collection or font name present, false otherwise
+ * @throws IOException In case of an I/O problem
+ */
+ public final List getTTCnames(FontFileReader in) throws IOException {
+ this.fontFile = in;
+
+ List fontNames = new ArrayList();
+ String tag = in.readTTFString(4);
+
+ if ("ttcf".equals(tag)) {
+ // This is a TrueType Collection
+ in.skip(4);
+
+ // Read directory offsets
+ int numDirectories = (int)in.readTTFULong();
+ long[] dirOffsets = new long[numDirectories];
+ for (int i = 0; i < numDirectories; i++) {
+ dirOffsets[i] = in.readTTFULong();
+ }
+
+ log.info("This is a TrueType collection file with "
+ + numDirectories + " fonts");
+ log.info("Containing the following fonts: ");
+
+ for (int i = 0; (i < numDirectories); i++) {
+ in.seekSet(dirOffsets[i]);
+ readDirTabs();
+
+ readName();
+
+ log.info(fullName);
+ fontNames.add(fullName);
+
+ // Reset names
+ notice = "";
+ fullName = "";
+ familyNames.clear();
+ postScriptName = "";
+ subFamilyName = "";
+ }
+
+ in.seekSet(0);
+ return fontNames;
+ } else {
+ log.error("Not a TTC!");
+ return null;
+ }
+ }
+
+ /*
+ * Helper classes, they are not very efficient, but that really
+ * doesn't matter...
+ */
+ private Integer[] unicodeToWinAnsi(int unicode) {
+ List ret = new ArrayList();
+ for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
+ if (unicode == Glyphs.WINANSI_ENCODING[i]) {
+ ret.add(new Integer(i));
+ }
+ }
+ return ret.toArray(new Integer[0]);
+ }
+
+ /**
+ * Dumps a few informational values to System.out.
+ */
+ public void printStuff() {
+ System.out.println("Font name: " + postScriptName);
+ System.out.println("Full name: " + fullName);
+ System.out.println("Family name: " + familyNames);
+ System.out.println("Subfamily name: " + subFamilyName);
+ System.out.println("Notice: " + notice);
+ System.out.println("xHeight: " + convertTTFUnit2PDFUnit(xHeight));
+ System.out.println("capheight: " + convertTTFUnit2PDFUnit(capHeight));
+
+ int italic = (int)(italicAngle >> 16);
+ System.out.println("Italic: " + italic);
+ System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000));
+ if ((italicAngle % 0x10000) > 0) {
+ System.out.print("."
+ + (short)((italicAngle % 0x10000) * 1000)
+ / 0x10000);
+ }
+ System.out.println();
+ System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender));
+ System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender));
+ System.out.println("FontBBox: [" + convertTTFUnit2PDFUnit(fontBBox1)
+ + " " + convertTTFUnit2PDFUnit(fontBBox2) + " "
+ + convertTTFUnit2PDFUnit(fontBBox3) + " "
+ + convertTTFUnit2PDFUnit(fontBBox4) + "]");
+ }
+
+ private String formatUnitsForDebug(int units) {
+ return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units";
+ }
+
+ /**
+ * Map a glyph index to the corresponding unicode code point
+ *
+ * @param glyphIndex
+ * @return unicode code point
+ */
+ private Integer glyphToUnicode(int glyphIndex) {
+ return glyphToUnicodeMap.get(new Integer(glyphIndex));
+ }
+
+ /**
+ * Map a unicode code point to the corresponding glyph index
+ *
+ * @param unicodeIndex unicode code point
+ * @return glyph index
+ */
+ private Integer unicodeToGlyph(int unicodeIndex) throws IOException {
+ final Integer result
+ = unicodeToGlyphMap.get(new Integer(unicodeIndex));
+ if (result == null) {
+ throw new IOException(
+ "Glyph index not found for unicode value " + unicodeIndex);
+ }
+ return result;
+ }
+
+ String getGlyphName(int glyphIndex) {
+ return mtxTab[glyphIndex].getName();
+ }
+
+ /**
+ * Determine if advanced (typographic) table is present.
+ * @return true if advanced (typographic) table is present
+ */
+ public boolean hasAdvancedTable() {
+ if (advancedTableReader != null) {
+ return advancedTableReader.hasAdvancedTable();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the GDEF table or null if none present.
+ * @return the GDEF table
+ */
+ public GlyphDefinitionTable getGDEF() {
+ if (advancedTableReader != null) {
+ return advancedTableReader.getGDEF();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the GSUB table or null if none present.
+ * @return the GSUB table
+ */
+ public GlyphSubstitutionTable getGSUB() {
+ if (advancedTableReader != null) {
+ return advancedTableReader.getGSUB();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the GPOS table or null if none present.
+ * @return the GPOS table
+ */
+ public GlyphPositioningTable getGPOS() {
+ if (advancedTableReader != null) {
+ return advancedTableReader.getGPOS();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Static main method to get info about a TrueType font.
+ * @param args The command line arguments
+ */
+ public static void main(String[] args) {
+ InputStream stream = null;
+ try {
+ boolean useKerning = true;
+ boolean useAdvanced = true;
+
+ stream = new FileInputStream(args[0]);
+ FontFileReader reader = new FontFileReader(stream);
+
+ String name = null;
+ if (args.length >= 2) {
+ name = args[1];
+ }
+
+ String header = OFFontLoader.readHeader(reader);
+ boolean isCFF = header.equals("OTTO");
+ OpenFont otfFile = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced);
+ otfFile.readFont(reader, header, name);
+ otfFile.printStuff();
+
+ } catch (IOException ioe) {
+ System.err.println("Problem reading font: " + ioe.toString());
+ ioe.printStackTrace(System.err);
+ } finally {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
deleted file mode 100644
index c273d4471..000000000
--- a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.truetype;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-
-/**
- * This class represents an entry to a TrueType font's Dir Tab.
- */
-public class TTFDirTabEntry {
-
- private byte[] tag = new byte[4];
- private int checksum;
- private long offset;
- private long length;
-
- public TTFDirTabEntry() {
- }
-
- public TTFDirTabEntry(long offset, long length) {
- this.offset = offset;
- this.length = length;
- }
-
- /**
- * Read Dir Tab.
- * @param in font file reader
- * @return tag name
- * @throws IOException upon I/O exception
- */
- public String read(FontFileReader in) throws IOException {
- tag[0] = in.readTTFByte();
- tag[1] = in.readTTFByte();
- tag[2] = in.readTTFByte();
- tag[3] = in.readTTFByte();
-
- in.skip(4); // Skip checksum
-
- offset = in.readTTFULong();
- length = in.readTTFULong();
- String tagStr = new String(tag, "ISO-8859-1");
-
- return tagStr;
- }
-
-
- @Override
- public String toString() {
- return "Read dir tab ["
- + tag[0] + " " + tag[1] + " " + tag[2] + " " + tag[3] + "]"
- + " offset: " + offset
- + " length: " + length
- + " name: " + tag;
- }
-
- /**
- * Returns the checksum.
- * @return int
- */
- public int getChecksum() {
- return checksum;
- }
-
- /**
- * Returns the length.
- * @return long
- */
- public long getLength() {
- return length;
- }
-
- /**
- * Returns the offset.
- * @return long
- */
- public long getOffset() {
- return offset;
- }
-
- /**
- * Returns the tag bytes.
- * @return byte[]
- */
- public byte[] getTag() {
- return tag;
- }
-
- /**
- * Returns the tag bytes.
- * @return byte[]
- */
- public String getTagString() {
- try {
- return new String(tag, "ISO-8859-1");
- } catch (UnsupportedEncodingException e) {
- return this.toString(); // Should never happen.
- }
- }
-
-}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java
index 2e78ef7f0..52df45ffb 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java
@@ -19,1409 +19,34 @@
package org.apache.fop.fonts.truetype;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import org.apache.xmlgraphics.fonts.Glyphs;
-
-import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException;
-import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
-import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
-import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
-import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader;
-import org.apache.fop.fonts.CMapSegment;
-import org.apache.fop.fonts.FontUtil;
-
-/**
- * Reads a TrueType file or a TrueType Collection.
- * The TrueType spec can be found at the Microsoft.
- * Typography site: http://www.microsoft.com/truetype/
- */
-public class TTFFile {
-
- static final byte NTABS = 24;
- static final int MAX_CHAR_CODE = 255;
- static final int ENC_BUF_SIZE = 1024;
-
- private static final String[] MAC_GLYPH_ORDERING = {
- /* 0x000 */
- ".notdef", ".null", "nonmarkingreturn", "space",
- "exclam", "quotedbl", "numbersign", "dollar",
- "percent", "ampersand", "quotesingle", "parenleft",
- "parenright", "asterisk", "plus", "comma",
- /* 0x010 */
- "hyphen", "period", "slash", "zero",
- "one", "two", "three", "four",
- "five", "six", "seven", "eight",
- "nine", "colon", "semicolon", "less",
- /* 0x020 */
- "equal", "greater", "question", "at",
- "A", "B", "C", "D",
- "E", "F", "G", "H",
- "I", "J", "K", "L",
- /* 0x030 */
- "M", "N", "O", "P",
- "Q", "R", "S", "T",
- "U", "V", "W", "X",
- "Y", "Z", "bracketleft", "backslash",
- /* 0x040 */
- "bracketright", "asciicircum", "underscore", "grave",
- "a", "b", "c", "d",
- "e", "f", "g", "h",
- "i", "j", "k", "l",
- /* 0x050 */
- "m", "n", "o", "p",
- "q", "r", "s", "t",
- "u", "v", "w", "x",
- "y", "z", "braceleft", "bar",
- /* 0x060 */
- "braceright", "asciitilde", "Adieresis", "Aring",
- "Ccedilla", "Eacute", "Ntilde", "Odieresis",
- "Udieresis", "aacute", "agrave", "acircumflex",
- "adieresis", "atilde", "aring", "ccedilla",
- /* 0x070 */
- "eacute", "egrave", "ecircumflex", "edieresis",
- "iacute", "igrave", "icircumflex", "idieresis",
- "ntilde", "oacute", "ograve", "ocircumflex",
- "odieresis", "otilde", "uacute", "ugrave",
- /* 0x080 */
- "ucircumflex", "udieresis", "dagger", "degree",
- "cent", "sterling", "section", "bullet",
- "paragraph", "germandbls", "registered", "copyright",
- "trademark", "acute", "dieresis", "notequal",
- /* 0x090 */
- "AE", "Oslash", "infinity", "plusminus",
- "lessequal", "greaterequal", "yen", "mu",
- "partialdiff", "summation", "product", "pi",
- "integral", "ordfeminine", "ordmasculine", "Omega",
- /* 0x0A0 */
- "ae", "oslash", "questiondown", "exclamdown",
- "logicalnot", "radical", "florin", "approxequal",
- "Delta", "guillemotleft", "guillemotright", "ellipsis",
- "nonbreakingspace", "Agrave", "Atilde", "Otilde",
- /* 0x0B0 */
- "OE", "oe", "endash", "emdash",
- "quotedblleft", "quotedblright", "quoteleft", "quoteright",
- "divide", "lozenge", "ydieresis", "Ydieresis",
- "fraction", "currency", "guilsinglleft", "guilsinglright",
- /* 0x0C0 */
- "fi", "fl", "daggerdbl", "periodcentered",
- "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
- "Ecircumflex", "Aacute", "Edieresis", "Egrave",
- "Iacute", "Icircumflex", "Idieresis", "Igrave",
- /* 0x0D0 */
- "Oacute", "Ocircumflex", "apple", "Ograve",
- "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
- "circumflex", "tilde", "macron", "breve",
- "dotaccent", "ring", "cedilla", "hungarumlaut",
- /* 0x0E0 */
- "ogonek", "caron", "Lslash", "lslash",
- "Scaron", "scaron", "Zcaron", "zcaron",
- "brokenbar", "Eth", "eth", "Yacute",
- "yacute", "Thorn", "thorn", "minus",
- /* 0x0F0 */
- "multiply", "onesuperior", "twosuperior", "threesuperior",
- "onehalf", "onequarter", "threequarters", "franc",
- "Gbreve", "gbreve", "Idotaccent", "Scedilla",
- "scedilla", "Cacute", "cacute", "Ccaron",
- /* 0x100 */
- "ccaron", "dcroat"
- };
-
- /** The FontFileReader used to read this TrueType font. */
- protected FontFileReader fontFile;
-
- /** Set to true to get even more debug output than with level DEBUG */
- public static final boolean TRACE_ENABLED = false;
-
- private final String encoding = "WinAnsiEncoding"; // Default encoding
-
- private final short firstChar = 0;
-
- private boolean useKerning = false;
-
- private boolean isEmbeddable = true;
- private boolean hasSerifs = true;
- /**
- * Table directory
- */
- protected Map dirTabs;
- private Map> kerningTab; // for CIDs
- private Map> ansiKerningTab; // For winAnsiEncoding
- private List cmaps;
- private Set unicodeMappings;
-
- private int upem; // unitsPerEm from "head" table
- private int nhmtx; // Number of horizontal metrics
- private PostScriptVersion postScriptVersion;
- private int locaFormat;
- /**
- * Offset to last loca
- */
- protected long lastLoca = 0;
- private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table)
-
- /**
- * Contains glyph data
- */
- protected TTFMtxEntry[] mtxTab; // Contains glyph data
-
- private String postScriptName = "";
- private String fullName = "";
- private String notice = "";
- private Set familyNames = new HashSet();
- private String subFamilyName = "";
-
- private long italicAngle = 0;
- private long isFixedPitch = 0;
- private int fontBBox1 = 0;
- private int fontBBox2 = 0;
- private int fontBBox3 = 0;
- private int fontBBox4 = 0;
- private int capHeight = 0;
- private int os2CapHeight = 0;
- private int underlinePosition = 0;
- private int underlineThickness = 0;
- private int xHeight = 0;
- private int os2xHeight = 0;
- //Effective ascender/descender
- private int ascender = 0;
- private int descender = 0;
- //Ascender/descender from hhea table
- private int hheaAscender = 0;
- private int hheaDescender = 0;
- //Ascender/descender from OS/2 table
- private int os2Ascender = 0;
- private int os2Descender = 0;
- private int usWeightClass = 0;
-
- private short lastChar = 0;
-
- private int[] ansiWidth;
- private Map> ansiIndex;
-
- // internal mapping of glyph indexes to unicode indexes
- // used for quick mappings in this class
- private final Map glyphToUnicodeMap = new HashMap();
- private final Map unicodeToGlyphMap = new HashMap();
-
- private TTFDirTabEntry currentDirTab;
-
- private boolean isCFF;
-
- // advanced typographic table support
- private boolean useAdvanced = false;
- private OTFAdvancedTypographicTableReader advancedTableReader;
-
- /**
- * logging instance
- */
- protected Log log = LogFactory.getLog(TTFFile.class);
-
- public TTFFile() {
- this(true, false);
- }
-
- /**
- * Constructor
- * @param useKerning true if kerning data should be loaded
- * @param useAdvanced true if advanced typographic tables should be loaded
- */
- public TTFFile(boolean useKerning, boolean useAdvanced) {
- this.useKerning = useKerning;
- this.useAdvanced = useAdvanced;
- }
-
- /**
- * Key-value helper class.
- */
- final class UnicodeMapping implements Comparable {
-
- private final int unicodeIndex;
- private final int glyphIndex;
-
- UnicodeMapping(int glyphIndex, int unicodeIndex) {
- this.unicodeIndex = unicodeIndex;
- this.glyphIndex = glyphIndex;
- glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex));
- unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex));
- }
-
- /**
- * Returns the glyphIndex.
- * @return the glyph index
- */
- public int getGlyphIndex() {
- return glyphIndex;
- }
-
- /**
- * Returns the unicodeIndex.
- * @return the Unicode index
- */
- public int getUnicodeIndex() {
- return unicodeIndex;
- }
-
-
- /** {@inheritDoc} */
- public int hashCode() {
- int hc = unicodeIndex;
- hc = 19 * hc + (hc ^ glyphIndex);
- return hc;
- }
-
- /** {@inheritDoc} */
- public boolean equals(Object o) {
- if (o instanceof UnicodeMapping) {
- UnicodeMapping m = (UnicodeMapping) o;
- if (unicodeIndex != m.unicodeIndex) {
- return false;
- } else {
- return (glyphIndex == m.glyphIndex);
- }
- } else {
- return false;
- }
- }
-
- /** {@inheritDoc} */
- public int compareTo(Object o) {
- if (o instanceof UnicodeMapping) {
- UnicodeMapping m = (UnicodeMapping) o;
- if (unicodeIndex > m.unicodeIndex) {
- return 1;
- } else if (unicodeIndex < m.unicodeIndex) {
- return -1;
- } else {
- return 0;
- }
- } else {
- return -1;
- }
- }
- }
-
- /**
- * Version of the PostScript table (post
) contained in this font.
- */
- public static enum PostScriptVersion {
- /** PostScript table version 1.0. */
- V1,
- /** PostScript table version 2.0. */
- V2,
- /** PostScript table version 3.0. */
- V3,
- /** Unknown version of the PostScript table. */
- UNKNOWN;
- }
-
- /**
- * Obtain directory table entry.
- * @param name (tag) of entry
- * @return a directory table entry or null if none found
- */
- public TTFDirTabEntry getDirectoryEntry(TTFTableName name) {
- return dirTabs.get(name);
- }
-
- /**
- * Position inputstream to position indicated
- * in the dirtab offset + offset
- * @param in font file reader
- * @param tableName (tag) of table
- * @param offset from start of table
- * @return true if seek succeeded
- * @throws IOException if I/O exception occurs during seek
- */
- public boolean seekTab(FontFileReader in, TTFTableName tableName,
- long offset) throws IOException {
- TTFDirTabEntry dt = dirTabs.get(tableName);
- if (dt == null) {
- log.error("Dirtab " + tableName.getName() + " not found.");
- return false;
- } else {
- in.seekSet(dt.getOffset() + offset);
- this.currentDirTab = dt;
- }
- return true;
- }
-
- /**
- * Convert from truetype unit to pdf unit based on the
- * unitsPerEm field in the "head" table
- * @param n truetype unit
- * @return pdf unit
- */
- public int convertTTFUnit2PDFUnit(int n) {
- int ret;
- if (n < 0) {
- long rest1 = n % upem;
- long storrest = 1000 * rest1;
- long ledd2 = (storrest != 0 ? rest1 / storrest : 0);
- ret = -((-1000 * n) / upem - (int)ledd2);
- } else {
- ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem;
- }
-
- return ret;
- }
-
- /**
- * Read the cmap table,
- * return false if the table is not present or only unsupported
- * tables are present. Currently only unicode cmaps are supported.
- * Set the unicodeIndex in the TTFMtxEntries and fills in the
- * cmaps vector.
- */
- private boolean readCMAP() throws IOException {
-
- unicodeMappings = new java.util.TreeSet();
-
- seekTab(fontFile, TTFTableName.CMAP, 2);
- int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables
- long cmapUniOffset = 0;
- long symbolMapOffset = 0;
-
- if (log.isDebugEnabled()) {
- log.debug(numCMap + " cmap tables");
- }
-
- //Read offset for all tables. We are only interested in the unicode table
- for (int i = 0; i < numCMap; i++) {
- int cmapPID = fontFile.readTTFUShort();
- int cmapEID = fontFile.readTTFUShort();
- long cmapOffset = fontFile.readTTFLong();
-
- if (log.isDebugEnabled()) {
- log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID);
- }
-
- if (cmapPID == 3 && cmapEID == 1) {
- cmapUniOffset = cmapOffset;
- }
- if (cmapPID == 3 && cmapEID == 0) {
- symbolMapOffset = cmapOffset;
- }
- }
-
- if (cmapUniOffset > 0) {
- return readUnicodeCmap(cmapUniOffset, 1);
- } else if (symbolMapOffset > 0) {
- return readUnicodeCmap(symbolMapOffset, 0);
- } else {
- log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table"
- + " not present. Aborting");
- return false;
- }
- }
-
- private boolean readUnicodeCmap(
- long cmapUniOffset, int encodingID)
- throws IOException {
- //Read CMAP table and correct mtxTab.index
- int mtxPtr = 0;
-
- // Read unicode cmap
- seekTab(fontFile, TTFTableName.CMAP, cmapUniOffset);
- int cmapFormat = fontFile.readTTFUShort();
- /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length
-
- if (log.isDebugEnabled()) {
- log.debug("CMAP format: " + cmapFormat);
- }
-
- if (cmapFormat == 4) {
- fontFile.skip(2); // Skip version number
- int cmapSegCountX2 = fontFile.readTTFUShort();
- int cmapSearchRange = fontFile.readTTFUShort();
- int cmapEntrySelector = fontFile.readTTFUShort();
- int cmapRangeShift = fontFile.readTTFUShort();
-
- if (log.isDebugEnabled()) {
- log.debug("segCountX2 : " + cmapSegCountX2);
- log.debug("searchRange : " + cmapSearchRange);
- log.debug("entrySelector: " + cmapEntrySelector);
- log.debug("rangeShift : " + cmapRangeShift);
- }
-
-
- int[] cmapEndCounts = new int[cmapSegCountX2 / 2];
- int[] cmapStartCounts = new int[cmapSegCountX2 / 2];
- int[] cmapDeltas = new int[cmapSegCountX2 / 2];
- int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2];
-
- for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
- cmapEndCounts[i] = fontFile.readTTFUShort();
- }
-
- fontFile.skip(2); // Skip reservedPad
-
- for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
- cmapStartCounts[i] = fontFile.readTTFUShort();
- }
-
- for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
- cmapDeltas[i] = fontFile.readTTFShort();
- }
-
- //int startRangeOffset = in.getCurrentPos();
-
- for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
- cmapRangeOffsets[i] = fontFile.readTTFUShort();
- }
-
- int glyphIdArrayOffset = fontFile.getCurrentPos();
-
- BitSet eightBitGlyphs = new BitSet(256);
-
- // Insert the unicode id for the glyphs in mtxTab
- // and fill in the cmaps ArrayList
-
- for (int i = 0; i < cmapStartCounts.length; i++) {
-
- if (log.isTraceEnabled()) {
- log.trace(i + ": " + cmapStartCounts[i]
- + " - " + cmapEndCounts[i]);
- }
- if (log.isDebugEnabled()) {
- if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) {
- log.debug("Font contains glyphs in the Unicode private use area: "
- + Integer.toHexString(cmapStartCounts[i]) + " - "
- + Integer.toHexString(cmapEndCounts[i]));
- }
- }
-
- for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) {
-
- // Update lastChar
- if (j < 256 && j > lastChar) {
- lastChar = (short)j;
- }
-
- if (j < 256) {
- eightBitGlyphs.set(j);
- }
-
- if (mtxPtr < mtxTab.length) {
- int glyphIdx;
- // the last character 65535 = .notdef
- // may have a range offset
- if (cmapRangeOffsets[i] != 0 && j != 65535) {
- int glyphOffset = glyphIdArrayOffset
- + ((cmapRangeOffsets[i] / 2)
- + (j - cmapStartCounts[i])
- + (i)
- - cmapSegCountX2 / 2) * 2;
- fontFile.seekSet(glyphOffset);
- glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i])
- & 0xffff;
-
- unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
- mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
-
- // Also add winAnsiWidth
- List v = ansiIndex.get(new Integer(j));
- if (v != null) {
- for (Integer aIdx : v) {
- ansiWidth[aIdx.intValue()]
- = mtxTab[glyphIdx].getWx();
-
- if (log.isTraceEnabled()) {
- log.trace("Added width "
- + mtxTab[glyphIdx].getWx()
- + " uni: " + j
- + " ansi: " + aIdx.intValue());
- }
- }
- }
-
- if (log.isTraceEnabled()) {
- log.trace("Idx: "
- + glyphIdx
- + " Delta: " + cmapDeltas[i]
- + " Unicode: " + j
- + " name: " + mtxTab[glyphIdx].getName());
- }
- } else {
- glyphIdx = (j + cmapDeltas[i]) & 0xffff;
-
- if (glyphIdx < mtxTab.length) {
- mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
- } else {
- log.debug("Glyph " + glyphIdx
- + " out of range: "
- + mtxTab.length);
- }
-
- unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
- if (glyphIdx < mtxTab.length) {
- mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
- } else {
- log.debug("Glyph " + glyphIdx
- + " out of range: "
- + mtxTab.length);
- }
-
- // Also add winAnsiWidth
- List v = ansiIndex.get(new Integer(j));
- if (v != null) {
- for (Integer aIdx : v) {
- ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx();
- }
- }
-
- //getLogger().debug("IIdx: " +
- // mtxPtr +
- // " Delta: " + cmap_deltas[i] +
- // " Unicode: " + j +
- // " name: " +
- // mtxTab[(j+cmap_deltas[i]) & 0xffff].name);
-
- }
- if (glyphIdx < mtxTab.length) {
- if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) {
- mtxPtr++;
- }
- }
- }
- }
- }
- } else {
- log.error("Cmap format not supported: " + cmapFormat);
- return false;
- }
- return true;
- }
-
- private boolean isInPrivateUseArea(int start, int end) {
- return (isInPrivateUseArea(start) || isInPrivateUseArea(end));
- }
-
- private boolean isInPrivateUseArea(int unicode) {
- return (unicode >= 0xE000 && unicode <= 0xF8FF);
- }
-
- /**
- * Print first char/last char
- */
- private void printMaxMin() {
- int min = 255;
- int max = 0;
- for (int i = 0; i < mtxTab.length; i++) {
- if (mtxTab[i].getIndex() < min) {
- min = mtxTab[i].getIndex();
- }
- if (mtxTab[i].getIndex() > max) {
- max = mtxTab[i].getIndex();
- }
- }
- log.info("Min: " + min);
- log.info("Max: " + max);
- }
-
-
- /**
- * Reads the font using a FontFileReader.
- *
- * @param in The FontFileReader to use
- * @throws IOException In case of an I/O problem
- */
- public void readFont(FontFileReader in) throws IOException {
- readFont(in, (String)null);
- }
-
- /**
- * initialize the ansiWidths array (for winAnsiEncoding)
- * and fill with the missingwidth
- */
- private void initAnsiWidths() {
- ansiWidth = new int[256];
- for (int i = 0; i < 256; i++) {
- ansiWidth[i] = mtxTab[0].getWx();
- }
-
- // Create an index hash to the ansiWidth
- // Can't just index the winAnsiEncoding when inserting widths
- // same char (eg bullet) is repeated more than one place
- ansiIndex = new HashMap>();
- for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
- Integer ansi = new Integer(i);
- Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]);
-
- List v = ansiIndex.get(uni);
- if (v == null) {
- v = new ArrayList();
- ansiIndex.put(uni, v);
- }
- v.add(ansi);
- }
- }
-
- /**
- * Read the font data.
- * If the fontfile is a TrueType Collection (.ttc file)
- * the name of the font to read data for must be supplied,
- * else the name is ignored.
- *
- * @param in The FontFileReader to use
- * @param name The name of the font
- * @return boolean Returns true if the font is valid
- * @throws IOException In case of an I/O problem
- */
- public boolean readFont(FontFileReader in, String name) throws IOException {
- fontFile = in;
- /*
- * Check if TrueType collection, and that the name
- * exists in the collection
- */
- if (!checkTTC(name)) {
- if (name == null) {
- throw new IllegalArgumentException(
- "For TrueType collection you must specify which font "
- + "to select (-ttcname)");
- } else {
- throw new IOException(
- "Name does not exist in the TrueType collection: " + name);
- }
- }
-
- readDirTabs();
- readFontHeader();
- getNumGlyphs();
- if (log.isDebugEnabled()) {
- log.debug("Number of glyphs in font: " + numberOfGlyphs);
- }
- readHorizontalHeader();
- readHorizontalMetrics();
- initAnsiWidths();
- readPostScript();
- readOS2();
- determineAscDesc();
- if (!isCFF) {
- readIndexToLocation();
- readGlyf();
- }
- readName();
- boolean pcltFound = readPCLT();
- // Read cmap table and fill in ansiwidths
- boolean valid = readCMAP();
- if (!valid) {
- return false;
- }
- // Create cmaps for bfentries
- createCMaps();
-
- if (useKerning) {
- readKerning();
- }
-
- // Read advanced typographic tables.
- if (useAdvanced) {
- try {
- OTFAdvancedTypographicTableReader atr
- = new OTFAdvancedTypographicTableReader(this, in);
- atr.readAll();
- this.advancedTableReader = atr;
- } catch (AdvancedTypographicTableFormatException e) {
- log.warn(
- "Encountered format constraint violation in advanced (typographic) table (AT) "
- + "in font '" + getFullName() + "', ignoring AT data: "
- + e.getMessage()
- );
- }
- }
-
- guessVerticalMetricsFromGlyphBBox();
- return true;
- }
-
- /**
- * Reads a font.
- *
- * @param in FontFileReader to read from
- * @param name Name to be checked for in the font file
- * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
- * new index as (Integer) value)
- * @throws IOException in case of an I/O problem
- */
- public void readFont(FontFileReader in, String name,
- Map glyphs) throws IOException {
- readFont(in, name);
- }
-
- private void createCMaps() {
- cmaps = new ArrayList();
- int unicodeStart;
- int glyphStart;
- int unicodeEnd;
-
- Iterator e = unicodeMappings.iterator();
- UnicodeMapping um = e.next();
- UnicodeMapping lastMapping = um;
-
- unicodeStart = um.getUnicodeIndex();
- glyphStart = um.getGlyphIndex();
-
- while (e.hasNext()) {
- um = e.next();
- if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex())
- || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) {
- unicodeEnd = lastMapping.getUnicodeIndex();
- cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart));
- unicodeStart = um.getUnicodeIndex();
- glyphStart = um.getGlyphIndex();
- }
- lastMapping = um;
- }
-
- unicodeEnd = lastMapping.getUnicodeIndex();
- cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart));
- }
-
- /**
- * Returns the PostScript name of the font.
- * @return String The PostScript name
- */
- public String getPostScriptName() {
- if (postScriptName.length() == 0) {
- return FontUtil.stripWhiteSpace(getFullName());
- } else {
- return postScriptName;
- }
- }
-
- PostScriptVersion getPostScriptVersion() {
- return postScriptVersion;
- }
-
- /**
- * Returns the font family names of the font.
- * @return Set The family names (a Set of Strings)
- */
- public Set getFamilyNames() {
- return familyNames;
- }
-
- /**
- * Returns the font sub family name of the font.
- * @return String The sub family name
- */
- public String getSubFamilyName() {
- return subFamilyName;
- }
-
- /**
- * Returns the full name of the font.
- * @return String The full name
- */
- public String getFullName() {
- return fullName;
- }
-
- /**
- * Returns the name of the character set used.
- * @return String The caracter set
- */
- public String getCharSetName() {
- return encoding;
- }
-
- /**
- * Returns the CapHeight attribute of the font.
- * @return int The CapHeight
- */
- public int getCapHeight() {
- return convertTTFUnit2PDFUnit(capHeight);
- }
-
- /**
- * Returns the XHeight attribute of the font.
- * @return int The XHeight
- */
- public int getXHeight() {
- return convertTTFUnit2PDFUnit(xHeight);
- }
-
- /**
- * Returns the number of bytes necessary to pad the currentPosition so that a table begins
- * on a 4-byte boundary.
- * @param currentPosition the position to pad.
- * @return int the number of bytes to pad.
- */
- protected int getPadSize(int currentPosition) {
- int padSize = 4 - (currentPosition % 4);
- return padSize < 4 ? padSize : 0;
- }
-
- /**
- * Returns the Flags attribute of the font.
- * @return int The Flags
- */
- public int getFlags() {
- int flags = 32; // Use Adobe Standard charset
- if (italicAngle != 0) {
- flags |= 64;
- }
- if (isFixedPitch != 0) {
- flags |= 2;
- }
- if (hasSerifs) {
- flags |= 1;
- }
- return flags;
- }
-
- /**
- * Returns the weight class of this font. Valid values are 100, 200....,800, 900.
- * @return the weight class value (or 0 if there was no OS/2 table in the font)
- */
- public int getWeightClass() {
- return this.usWeightClass;
- }
-
- /**
- * Returns the StemV attribute of the font.
- * @return String The StemV
- */
- public String getStemV() {
- return "0";
- }
-
- /**
- * Returns the ItalicAngle attribute of the font.
- * @return String The ItalicAngle
- */
- public String getItalicAngle() {
- String ia = Short.toString((short)(italicAngle / 0x10000));
-
- // This is the correct italic angle, however only int italic
- // angles are supported at the moment so this is commented out.
- /*
- * if ((italicAngle % 0x10000) > 0 )
- * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000)));
- */
- return ia;
- }
-
- /**
- * @return int[] The font bbox
- */
- public int[] getFontBBox() {
- final int[] fbb = new int[4];
- fbb[0] = convertTTFUnit2PDFUnit(fontBBox1);
- fbb[1] = convertTTFUnit2PDFUnit(fontBBox2);
- fbb[2] = convertTTFUnit2PDFUnit(fontBBox3);
- fbb[3] = convertTTFUnit2PDFUnit(fontBBox4);
-
- return fbb;
- }
-
- /**
- * Returns the LowerCaseAscent attribute of the font.
- * @return int The LowerCaseAscent
- */
- public int getLowerCaseAscent() {
- return convertTTFUnit2PDFUnit(ascender);
- }
-
- /**
- * Returns the LowerCaseDescent attribute of the font.
- * @return int The LowerCaseDescent
- */
- public int getLowerCaseDescent() {
- return convertTTFUnit2PDFUnit(descender);
- }
-
- /**
- * Returns the index of the last character, but this is for WinAnsiEncoding
- * only, so the last char is < 256.
- * @return short Index of the last character (<256)
- */
- public short getLastChar() {
- return lastChar;
- }
-
- /**
- * Returns the index of the first character.
- * @return short Index of the first character
- */
- public short getFirstChar() {
- return firstChar;
- }
-
- /**
- * Returns an array of character widths.
- * @return int[] The character widths
- */
- public int[] getWidths() {
- int[] wx = new int[mtxTab.length];
- for (int i = 0; i < wx.length; i++) {
- wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx());
- }
-
- return wx;
- }
-
- /**
- * Returns an array (xMin, yMin, xMax, yMax) for a glyph.
- *
- * @param glyphIndex the index of the glyph
- * @return int[] Array defining bounding box.
- */
- public int[] getBBox(int glyphIndex) {
- int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox();
- int[] bbox = new int[4];
- for (int i = 0; i < 4; i++) {
- bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]);
- }
- return bbox;
- }
-
- /**
- * Returns the width of a given character.
- * @param idx Index of the character
- * @return int Standard width
- */
- public int getCharWidth(int idx) {
- return convertTTFUnit2PDFUnit(ansiWidth[idx]);
- }
-
- /**
- * Returns the kerning table.
- * @return Map The kerning table
- */
- public Map> getKerning() {
- return kerningTab;
- }
-
- /**
- * Returns the ANSI kerning table.
- * @return Map The ANSI kerning table
- */
- public Map> getAnsiKerning() {
- return ansiKerningTab;
- }
-
- /**
- * Indicates if the font may be embedded.
- * @return boolean True if it may be embedded
- */
- public boolean isEmbeddable() {
- return isEmbeddable;
- }
-
- /**
- * Indicates whether or not the font is an OpenType
- * CFF font (rather than a TrueType font).
- * @return true if the font is in OpenType CFF format.
- */
- public boolean isCFF() {
- return this.isCFF;
- }
-
- /**
- * Read Table Directory from the current position in the
- * FontFileReader and fill the global HashMap dirTabs
- * with the table name (String) as key and a TTFDirTabEntry
- * as value.
- * @throws IOException in case of an I/O problem
- */
- protected void readDirTabs() throws IOException {
- int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes)
- switch (sfntVersion) {
- case 0x10000:
- log.debug("sfnt version: OpenType 1.0");
- break;
- case 0x4F54544F: //"OTTO"
- this.isCFF = true;
- log.debug("sfnt version: OpenType with CFF data");
- break;
- case 0x74727565: //"true"
- log.debug("sfnt version: Apple TrueType");
- break;
- case 0x74797031: //"typ1"
- log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper");
- break;
- default:
- log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion));
- break;
- }
- int ntabs = fontFile.readTTFUShort();
- fontFile.skip(6); // 3xTTF_USHORT_SIZE
-
- dirTabs = new HashMap();
- TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs];
- log.debug("Reading " + ntabs + " dir tables");
-
- for (int i = 0; i < ntabs; i++) {
- pd[i] = new TTFDirTabEntry();
- String tableName = pd[i].read(fontFile);
- dirTabs.put(TTFTableName.getValue(tableName), pd[i]);
- }
- dirTabs.put(TTFTableName.TABLE_DIRECTORY,
- new TTFDirTabEntry(0L, fontFile.getCurrentPos()));
- log.debug("dir tables: " + dirTabs.keySet());
- }
-
- /**
- * Read the "head" table, this reads the bounding box and
- * sets the upem (unitsPerEM) variable
- * @throws IOException in case of an I/O problem
- */
- protected void readFontHeader() throws IOException {
- seekTab(fontFile, TTFTableName.HEAD, 2 * 4 + 2 * 4);
- int flags = fontFile.readTTFUShort();
- if (log.isDebugEnabled()) {
- log.debug("flags: " + flags + " - " + Integer.toString(flags, 2));
- }
- upem = fontFile.readTTFUShort();
- if (log.isDebugEnabled()) {
- log.debug("unit per em: " + upem);
- }
-
- fontFile.skip(16);
-
- fontBBox1 = fontFile.readTTFShort();
- fontBBox2 = fontFile.readTTFShort();
- fontBBox3 = fontFile.readTTFShort();
- fontBBox4 = fontFile.readTTFShort();
- if (log.isDebugEnabled()) {
- log.debug("font bbox: xMin=" + fontBBox1
- + " yMin=" + fontBBox2
- + " xMax=" + fontBBox3
- + " yMax=" + fontBBox4);
- }
-
- fontFile.skip(2 + 2 + 2);
-
- locaFormat = fontFile.readTTFShort();
- }
-
- /**
- * Read the number of glyphs from the "maxp" table
- * @throws IOException in case of an I/O problem
- */
- protected void getNumGlyphs() throws IOException {
- seekTab(fontFile, TTFTableName.MAXP, 4);
- numberOfGlyphs = fontFile.readTTFUShort();
- }
-
-
- /**
- * Read the "hhea" table to find the ascender and descender and
- * size of "hmtx" table, as a fixed size font might have only
- * one width.
- * @throws IOException in case of an I/O problem
- */
- protected void readHorizontalHeader()
- throws IOException {
- seekTab(fontFile, TTFTableName.HHEA, 4);
- hheaAscender = fontFile.readTTFShort();
- hheaDescender = fontFile.readTTFShort();
-
- fontFile.skip(2 + 2 + 3 * 2 + 8 * 2);
- nhmtx = fontFile.readTTFUShort();
-
- if (log.isDebugEnabled()) {
- log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender));
- log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender));
- log.debug("Number of horizontal metrics: " + nhmtx);
- }
- }
-
- /**
- * Read "hmtx" table and put the horizontal metrics
- * in the mtxTab array. If the number of metrics is less
- * than the number of glyphs (eg fixed size fonts), extend
- * the mtxTab array and fill in the missing widths
- * @throws IOException in case of an I/O problem
- */
- protected void readHorizontalMetrics()
- throws IOException {
- seekTab(fontFile, TTFTableName.HMTX, 0);
-
- int mtxSize = Math.max(numberOfGlyphs, nhmtx);
- mtxTab = new TTFMtxEntry[mtxSize];
-
- if (log.isTraceEnabled()) {
- log.trace("*** Widths array: \n");
- }
- for (int i = 0; i < mtxSize; i++) {
- mtxTab[i] = new TTFMtxEntry();
- }
- for (int i = 0; i < nhmtx; i++) {
- mtxTab[i].setWx(fontFile.readTTFUShort());
- mtxTab[i].setLsb(fontFile.readTTFUShort());
-
- if (log.isTraceEnabled()) {
- log.trace(" width[" + i + "] = "
- + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
- }
- }
-
- if (nhmtx < mtxSize) {
- // Fill in the missing widths
- int lastWidth = mtxTab[nhmtx - 1].getWx();
- for (int i = nhmtx; i < mtxSize; i++) {
- mtxTab[i].setWx(lastWidth);
- mtxTab[i].setLsb(fontFile.readTTFUShort());
- }
- }
- }
-
-
- /**
- * Read the "post" table
- * containing the PostScript names of the glyphs.
- */
- private void readPostScript() throws IOException {
- seekTab(fontFile, TTFTableName.POST, 0);
- int postFormat = fontFile.readTTFLong();
- italicAngle = fontFile.readTTFULong();
- underlinePosition = fontFile.readTTFShort();
- underlineThickness = fontFile.readTTFShort();
- isFixedPitch = fontFile.readTTFULong();
-
- //Skip memory usage values
- fontFile.skip(4 * 4);
-
- log.debug("PostScript format: 0x" + Integer.toHexString(postFormat));
- switch (postFormat) {
- case 0x00010000:
- log.debug("PostScript format 1");
- postScriptVersion = PostScriptVersion.V1;
- for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) {
- mtxTab[i].setName(MAC_GLYPH_ORDERING[i]);
- }
- break;
- case 0x00020000:
- log.debug("PostScript format 2");
- postScriptVersion = PostScriptVersion.V2;
- int numGlyphStrings = 0;
-
- // Read Number of Glyphs
- int l = fontFile.readTTFUShort();
-
- // Read indexes
- for (int i = 0; i < l; i++) {
- mtxTab[i].setIndex(fontFile.readTTFUShort());
-
- if (mtxTab[i].getIndex() > 257) {
- //Index is not in the Macintosh standard set
- numGlyphStrings++;
- }
-
- if (log.isTraceEnabled()) {
- log.trace("PostScript index: " + mtxTab[i].getIndexAsString());
- }
- }
-
- // firstChar=minIndex;
- String[] psGlyphsBuffer = new String[numGlyphStrings];
- if (log.isDebugEnabled()) {
- log.debug("Reading " + numGlyphStrings
- + " glyphnames, that are not in the standard Macintosh"
- + " set. Total number of glyphs=" + l);
- }
- for (int i = 0; i < psGlyphsBuffer.length; i++) {
- psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte());
- }
-
- //Set glyph names
- for (int i = 0; i < l; i++) {
- if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) {
- mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]);
- } else {
- if (!mtxTab[i].isIndexReserved()) {
- int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length;
-
- if (log.isTraceEnabled()) {
- log.trace(k + " i=" + i + " mtx=" + mtxTab.length
- + " ps=" + psGlyphsBuffer.length);
- }
-
- mtxTab[i].setName(psGlyphsBuffer[k]);
- }
- }
- }
-
- break;
- case 0x00030000:
- // PostScript format 3 contains no glyph names
- log.debug("PostScript format 3");
- postScriptVersion = PostScriptVersion.V3;
- break;
- default:
- log.error("Unknown PostScript format: " + postFormat);
- postScriptVersion = PostScriptVersion.UNKNOWN;
- }
- }
-
-
- /**
- * Read the "OS/2" table
- */
- private void readOS2() throws IOException {
- // Check if font is embeddable
- TTFDirTabEntry os2Entry = dirTabs.get(TTFTableName.OS2);
- if (os2Entry != null) {
- seekTab(fontFile, TTFTableName.OS2, 0);
- int version = fontFile.readTTFUShort();
- if (log.isDebugEnabled()) {
- log.debug("OS/2 table: version=" + version
- + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength());
- }
- fontFile.skip(2); //xAvgCharWidth
- this.usWeightClass = fontFile.readTTFUShort();
-
- // usWidthClass
- fontFile.skip(2);
-
- int fsType = fontFile.readTTFUShort();
- if (fsType == 2) {
- isEmbeddable = false;
- } else {
- isEmbeddable = true;
- }
- fontFile.skip(11 * 2);
- fontFile.skip(10); //panose array
- fontFile.skip(4 * 4); //unicode ranges
- fontFile.skip(4);
- fontFile.skip(3 * 2);
- int v;
- os2Ascender = fontFile.readTTFShort(); //sTypoAscender
- os2Descender = fontFile.readTTFShort(); //sTypoDescender
- if (log.isDebugEnabled()) {
- log.debug("sTypoAscender: " + os2Ascender
- + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender));
- log.debug("sTypoDescender: " + os2Descender
- + " -> internal " + convertTTFUnit2PDFUnit(os2Descender));
- }
- v = fontFile.readTTFShort(); //sTypoLineGap
- if (log.isDebugEnabled()) {
- log.debug("sTypoLineGap: " + v);
- }
- v = fontFile.readTTFUShort(); //usWinAscent
- if (log.isDebugEnabled()) {
- log.debug("usWinAscent: " + formatUnitsForDebug(v));
- }
- v = fontFile.readTTFUShort(); //usWinDescent
- if (log.isDebugEnabled()) {
- log.debug("usWinDescent: " + formatUnitsForDebug(v));
- }
-
- //version 1 OS/2 table might end here
- if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) {
- fontFile.skip(2 * 4);
- this.os2xHeight = fontFile.readTTFShort(); //sxHeight
- this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight
- if (log.isDebugEnabled()) {
- log.debug("sxHeight: " + this.os2xHeight);
- log.debug("sCapHeight: " + this.os2CapHeight);
- }
- }
-
- } else {
- isEmbeddable = true;
- }
- }
-
- /**
- * Read the "loca" table.
- * @throws IOException In case of a I/O problem
- */
- protected final void readIndexToLocation()
- throws IOException {
- if (!seekTab(fontFile, TTFTableName.LOCA, 0)) {
- throw new IOException("'loca' table not found, happens when the font file doesn't"
- + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)");
- }
- for (int i = 0; i < numberOfGlyphs; i++) {
- mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong()
- : (fontFile.readTTFUShort() << 1));
- }
- lastLoca = (locaFormat == 1 ? fontFile.readTTFULong()
- : (fontFile.readTTFUShort() << 1));
+/**
+ * Reads a TrueType file or a TrueType Collection.
+ * The TrueType spec can be found at the Microsoft.
+ * Typography site: http://www.microsoft.com/truetype/
+ */
+public class TTFFile extends OpenFont {
+
+ public TTFFile() {
+ this(true, false);
}
/**
- * Read the "glyf" table to find the bounding boxes.
- * @throws IOException In case of a I/O problem
+ * Constructor
+ * @param useKerning true if kerning data should be loaded
+ * @param useAdvanced true if advanced typographic tables should be loaded
*/
- private void readGlyf() throws IOException {
- TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.GLYF);
- if (dirTab == null) {
- throw new IOException("glyf table not found, cannot continue");
- }
- for (int i = 0; i < (numberOfGlyphs - 1); i++) {
- if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) {
- fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset());
- fontFile.skip(2);
- final int[] bbox = {
- fontFile.readTTFShort(),
- fontFile.readTTFShort(),
- fontFile.readTTFShort(),
- fontFile.readTTFShort()};
- mtxTab[i].setBoundingBox(bbox);
- } else {
- mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox());
- }
- }
-
-
- long n = (dirTabs.get(TTFTableName.GLYF)).getOffset();
- for (int i = 0; i < numberOfGlyphs; i++) {
- if ((i + 1) >= mtxTab.length
- || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) {
- fontFile.seekSet(n + mtxTab[i].getOffset());
- fontFile.skip(2);
- final int[] bbox = {
- fontFile.readTTFShort(),
- fontFile.readTTFShort(),
- fontFile.readTTFShort(),
- fontFile.readTTFShort()};
- mtxTab[i].setBoundingBox(bbox);
- } else {
- /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/
- final int bbox0 = mtxTab[0].getBoundingBox()[0];
- final int[] bbox = {bbox0, bbox0, bbox0, bbox0};
- mtxTab[i].setBoundingBox(bbox);
- /* Original code
- mtxTab[i].bbox[0] = mtxTab[0].bbox[0];
- mtxTab[i].bbox[1] = mtxTab[0].bbox[0];
- mtxTab[i].bbox[2] = mtxTab[0].bbox[0];
- mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */
- }
- if (log.isTraceEnabled()) {
- log.trace(mtxTab[i].toString(this));
- }
- }
+ public TTFFile(boolean useKerning, boolean useAdvanced) {
+ super(useKerning, useAdvanced);
}
/**
* Read the "name" table.
* @throws IOException In case of a I/O problem
*/
- private void readName() throws IOException {
- seekTab(fontFile, TTFTableName.NAME, 2);
+ protected void readName() throws IOException {
+ seekTab(fontFile, OFTableName.NAME, 2);
int i = fontFile.getCurrentPos();
int n = fontFile.readTTFUShort();
int j = fontFile.readTTFUShort() + i - 2;
@@ -1487,572 +112,84 @@ public class TTFFile {
}
/**
- * Read the "PCLT" table to find xHeight and capHeight.
+ * Read the "glyf" table to find the bounding boxes.
* @throws IOException In case of a I/O problem
*/
- private boolean readPCLT() throws IOException {
- TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.PCLT);
- if (dirTab != null) {
- fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2);
- xHeight = fontFile.readTTFUShort();
- log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight));
- fontFile.skip(2 * 2);
- capHeight = fontFile.readTTFUShort();
- log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight));
- fontFile.skip(2 + 16 + 8 + 6 + 1 + 1);
-
- int serifStyle = fontFile.readTTFUByte();
- serifStyle = serifStyle >> 6;
- serifStyle = serifStyle & 3;
- if (serifStyle == 1) {
- hasSerifs = false;
- } else {
- hasSerifs = true;
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Determines the right source for the ascender and descender values. The problem here is
- * that the interpretation of these values is not the same for every font. There doesn't seem
- * to be a uniform definition of an ascender and a descender. In some fonts
- * the hhea values are defined after the Apple interpretation, but not in every font. The
- * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the
- * baseline so we need values which add up more or less to the "em box". However, due to
- * accent modifiers a character can grow beyond the em box.
- */
- private void determineAscDesc() {
- int hheaBoxHeight = hheaAscender - hheaDescender;
- int os2BoxHeight = os2Ascender - os2Descender;
- if (os2Ascender > 0 && os2BoxHeight <= upem) {
- ascender = os2Ascender;
- descender = os2Descender;
- } else if (hheaAscender > 0 && hheaBoxHeight <= upem) {
- ascender = hheaAscender;
- descender = hheaDescender;
- } else {
- if (os2Ascender > 0) {
- //Fall back to info from OS/2 if possible
- ascender = os2Ascender;
- descender = os2Descender;
- } else {
- ascender = hheaAscender;
- descender = hheaDescender;
- }
- }
-
- if (log.isDebugEnabled()) {
- log.debug("Font box height: " + (ascender - descender));
- if (ascender - descender > upem) {
- log.debug("Ascender and descender together are larger than the em box.");
- }
+ private void readGlyf() throws IOException {
+ OFDirTabEntry dirTab = dirTabs.get(OFTableName.GLYF);
+ if (dirTab == null) {
+ throw new IOException("glyf table not found, cannot continue");
}
- }
-
- private void guessVerticalMetricsFromGlyphBBox() {
- // Approximate capHeight from height of "H"
- // It's most unlikely that a font misses the PCLT table
- // This also assumes that postscriptnames exists ("H")
- // Should look it up in the cmap (that wouldn't help
- // for charsets without H anyway...)
- // Same for xHeight with the letter "x"
- int localCapHeight = 0;
- int localXHeight = 0;
- int localAscender = 0;
- int localDescender = 0;
- for (int i = 0; i < mtxTab.length; i++) {
- if ("H".equals(mtxTab[i].getName())) {
- localCapHeight = mtxTab[i].getBoundingBox()[3];
- } else if ("x".equals(mtxTab[i].getName())) {
- localXHeight = mtxTab[i].getBoundingBox()[3];
- } else if ("d".equals(mtxTab[i].getName())) {
- localAscender = mtxTab[i].getBoundingBox()[3];
- } else if ("p".equals(mtxTab[i].getName())) {
- localDescender = mtxTab[i].getBoundingBox()[1];
+ for (int i = 0; i < (numberOfGlyphs - 1); i++) {
+ if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) {
+ fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset());
+ fontFile.skip(2);
+ final int[] bbox = {
+ fontFile.readTTFShort(),
+ fontFile.readTTFShort(),
+ fontFile.readTTFShort(),
+ fontFile.readTTFShort()};
+ mtxTab[i].setBoundingBox(bbox);
} else {
- // OpenType Fonts with a version 3.0 "post" table don't have glyph names.
- // Use Unicode indices instead.
- List unicodeIndex = mtxTab[i].getUnicodeIndex();
- if (unicodeIndex.size() > 0) {
- //Only the first index is used
- char ch = (char)((Integer)unicodeIndex.get(0)).intValue();
- if (ch == 'H') {
- localCapHeight = mtxTab[i].getBoundingBox()[3];
- } else if (ch == 'x') {
- localXHeight = mtxTab[i].getBoundingBox()[3];
- } else if (ch == 'd') {
- localAscender = mtxTab[i].getBoundingBox()[3];
- } else if (ch == 'p') {
- localDescender = mtxTab[i].getBoundingBox()[1];
- }
- }
- }
- }
- if (log.isDebugEnabled()) {
- log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender));
- log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender));
- }
- if (ascender - descender > upem) {
- log.debug("Replacing specified ascender/descender with derived values to get values"
- + " which fit in the em box.");
- ascender = localAscender;
- descender = localDescender;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight));
- log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight));
- }
- if (capHeight == 0) {
- capHeight = localCapHeight;
- if (capHeight == 0) {
- capHeight = os2CapHeight;
- }
- if (capHeight == 0) {
- log.debug("capHeight value could not be determined."
- + " The font may not work as expected.");
- }
- }
- if (xHeight == 0) {
- xHeight = localXHeight;
- if (xHeight == 0) {
- xHeight = os2xHeight;
- }
- if (xHeight == 0) {
- log.debug("xHeight value could not be determined."
- + " The font may not work as expected.");
- }
- }
- }
-
- /**
- * Read the kerning table, create a table for both CIDs and
- * winAnsiEncoding.
- * @throws IOException In case of a I/O problem
- */
- private void readKerning() throws IOException {
- // Read kerning
- kerningTab = new HashMap>();
- ansiKerningTab = new HashMap>();
- TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.KERN);
- if (dirTab != null) {
- seekTab(fontFile, TTFTableName.KERN, 2);
- for (int n = fontFile.readTTFUShort(); n > 0; n--) {
- fontFile.skip(2 * 2);
- int k = fontFile.readTTFUShort();
- if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) {
- return;
- }
- if ((k >> 8) != 0) {
- continue;
- }
-
- k = fontFile.readTTFUShort();
- fontFile.skip(3 * 2);
- while (k-- > 0) {
- int i = fontFile.readTTFUShort();
- int j = fontFile.readTTFUShort();
- int kpx = fontFile.readTTFShort();
- if (kpx != 0) {
- // CID kerning table entry, using unicode indexes
- final Integer iObj = glyphToUnicode(i);
- final Integer u2 = glyphToUnicode(j);
- if (iObj == null) {
- // happens for many fonts (Ubuntu font set),
- // stray entries in the kerning table??
- log.debug("Ignoring kerning pair because no Unicode index was"
- + " found for the first glyph " + i);
- } else if (u2 == null) {
- log.debug("Ignoring kerning pair because Unicode index was"
- + " found for the second glyph " + i);
- } else {
- Map adjTab = kerningTab.get(iObj);
- if (adjTab == null) {
- adjTab = new HashMap();
- }
- adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx)));
- kerningTab.put(iObj, adjTab);
- }
- }
- }
- }
-
- // Create winAnsiEncoded kerning table from kerningTab
- // (could probably be simplified, for now we remap back to CID indexes and
- // then to winAnsi)
- for (Integer unicodeKey1 : kerningTab.keySet()) {
- Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue());
- Map akpx = new HashMap();
- Map ckpx = kerningTab.get(unicodeKey1);
-
- for (Integer unicodeKey2 : ckpx.keySet()) {
- Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue());
- Integer kern = ckpx.get(unicodeKey2);
-
- Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator();
- while (uniMap.hasNext()) {
- Integer unicodeKey = (Integer)uniMap.next();
- Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
- for (int u = 0; u < ansiKeys.length; u++) {
- akpx.put(ansiKeys[u], kern);
- }
- }
- }
-
- if (akpx.size() > 0) {
- Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator();
- while (uniMap.hasNext()) {
- Integer unicodeKey = (Integer)uniMap.next();
- Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
- for (int u = 0; u < ansiKeys.length; u++) {
- ansiKerningTab.put(ansiKeys[u], akpx);
- }
- }
- }
+ mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox());
}
}
- }
- /**
- * Streams a font.
- * @param ttfOut The interface for streaming TrueType tables.
- * @exception IOException file write error
- */
- public void stream(TTFOutputStream ttfOut) throws IOException {
- SortedSet> sortedDirTabs = sortDirTabMap(dirTabs);
- byte[] file = fontFile.getAllBytes();
- TTFTableOutputStream tableOut = ttfOut.getTableOutputStream();
- TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream();
- ttfOut.startFontStream();
- for (Map.Entry entry : sortedDirTabs) {
- int offset = (int) entry.getValue().getOffset();
- int paddedLength = (int) entry.getValue().getLength();
- paddedLength += getPadSize(offset + paddedLength);
- if (entry.getKey().equals(TTFTableName.GLYF)) {
- streamGlyf(glyphOut, file, offset, paddedLength);
+ long n = (dirTabs.get(OFTableName.GLYF)).getOffset();
+ for (int i = 0; i < numberOfGlyphs; i++) {
+ if ((i + 1) >= mtxTab.length
+ || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) {
+ fontFile.seekSet(n + mtxTab[i].getOffset());
+ fontFile.skip(2);
+ final int[] bbox = {
+ fontFile.readTTFShort(),
+ fontFile.readTTFShort(),
+ fontFile.readTTFShort(),
+ fontFile.readTTFShort()};
+ mtxTab[i].setBoundingBox(bbox);
} else {
- tableOut.streamTable(file, offset, paddedLength);
- }
- }
- ttfOut.endFontStream();
- }
-
- private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset,
- int tableLength) throws IOException {
- //Stream all but the last glyph
- int glyphStart = 0;
- int glyphEnd = 0;
- glyphOut.startGlyphStream();
- for (int i = 0; i < mtxTab.length - 1; i++) {
- glyphStart = (int) mtxTab[i].getOffset() + tableOffset;
- glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset;
- glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart);
- }
- glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd);
- glyphOut.endGlyphStream();
- }
-
- /**
- * Returns the order in which the tables in a TrueType font should be written to file.
- * @param directoryTabs the map that is to be sorted.
- * @return TTFTablesNames[] an array of table names sorted in the order they should appear in
- * the TTF file.
- */
- SortedSet>
- sortDirTabMap(Map directoryTabs) {
- SortedSet> sortedSet
- = new TreeSet>(
- new Comparator>() {
-
- public int compare(Entry o1,
- Entry o2) {
- return (int) (o1.getValue().getOffset() - o2.getValue().getOffset());
- }
- });
- sortedSet.addAll(directoryTabs.entrySet());
- return sortedSet;
- }
-
- /**
- * Returns this font's character to glyph mapping.
- *
- * @return the font's cmap
- */
- public List getCMaps() {
- return cmaps;
- }
-
- /**
- * Check if this is a TrueType collection and that the given
- * name exists in the collection.
- * If it does, set offset in fontfile to the beginning of
- * the Table Directory for that font.
- * @param name The name to check
- * @return True if not collection or font name present, false otherwise
- * @throws IOException In case of an I/O problem
- */
- protected final boolean checkTTC(String name) throws IOException {
- String tag = fontFile.readTTFString(4);
-
- if ("ttcf".equals(tag)) {
- // This is a TrueType Collection
- fontFile.skip(4);
-
- // Read directory offsets
- int numDirectories = (int)fontFile.readTTFULong();
- // int numDirectories=in.readTTFUShort();
- long[] dirOffsets = new long[numDirectories];
- for (int i = 0; i < numDirectories; i++) {
- dirOffsets[i] = fontFile.readTTFULong();
- }
-
- log.info("This is a TrueType collection file with "
- + numDirectories + " fonts");
- log.info("Containing the following fonts: ");
- // Read all the directories and name tables to check
- // If the font exists - this is a bit ugly, but...
- boolean found = false;
-
- // Iterate through all name tables even if font
- // Is found, just to show all the names
- long dirTabOffset = 0;
- for (int i = 0; (i < numDirectories); i++) {
- fontFile.seekSet(dirOffsets[i]);
- readDirTabs();
-
- readName();
-
- if (fullName.equals(name)) {
- found = true;
- dirTabOffset = dirOffsets[i];
- log.info(fullName + " <-- selected");
- } else {
- log.info(fullName);
- }
-
- // Reset names
- notice = "";
- fullName = "";
- familyNames.clear();
- postScriptName = "";
- subFamilyName = "";
- }
-
- fontFile.seekSet(dirTabOffset);
- return found;
- } else {
- fontFile.seekSet(0);
- return true;
- }
- }
-
- /**
- * Return TTC font names
- * @param in FontFileReader to read from
- * @return True if not collection or font name present, false otherwise
- * @throws IOException In case of an I/O problem
- */
- public final List getTTCnames(FontFileReader in) throws IOException {
- this.fontFile = in;
-
- List fontNames = new ArrayList();
- String tag = in.readTTFString(4);
-
- if ("ttcf".equals(tag)) {
- // This is a TrueType Collection
- in.skip(4);
-
- // Read directory offsets
- int numDirectories = (int)in.readTTFULong();
- long[] dirOffsets = new long[numDirectories];
- for (int i = 0; i < numDirectories; i++) {
- dirOffsets[i] = in.readTTFULong();
- }
-
- log.info("This is a TrueType collection file with "
- + numDirectories + " fonts");
- log.info("Containing the following fonts: ");
-
- for (int i = 0; (i < numDirectories); i++) {
- in.seekSet(dirOffsets[i]);
- readDirTabs();
-
- readName();
-
- log.info(fullName);
- fontNames.add(fullName);
-
- // Reset names
- notice = "";
- fullName = "";
- familyNames.clear();
- postScriptName = "";
- subFamilyName = "";
+ /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/
+ final int bbox0 = mtxTab[0].getBoundingBox()[0];
+ final int[] bbox = {bbox0, bbox0, bbox0, bbox0};
+ mtxTab[i].setBoundingBox(bbox);
+ /* Original code
+ mtxTab[i].bbox[0] = mtxTab[0].bbox[0];
+ mtxTab[i].bbox[1] = mtxTab[0].bbox[0];
+ mtxTab[i].bbox[2] = mtxTab[0].bbox[0];
+ mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */
}
-
- in.seekSet(0);
- return fontNames;
- } else {
- log.error("Not a TTC!");
- return null;
- }
- }
-
- /*
- * Helper classes, they are not very efficient, but that really
- * doesn't matter...
- */
- private Integer[] unicodeToWinAnsi(int unicode) {
- List ret = new ArrayList();
- for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
- if (unicode == Glyphs.WINANSI_ENCODING[i]) {
- ret.add(new Integer(i));
+ if (log.isTraceEnabled()) {
+ log.trace(mtxTab[i].toString(this));
}
}
- return ret.toArray(new Integer[0]);
- }
-
- /**
- * Dumps a few informational values to System.out.
- */
- public void printStuff() {
- System.out.println("Font name: " + postScriptName);
- System.out.println("Full name: " + fullName);
- System.out.println("Family name: " + familyNames);
- System.out.println("Subfamily name: " + subFamilyName);
- System.out.println("Notice: " + notice);
- System.out.println("xHeight: " + convertTTFUnit2PDFUnit(xHeight));
- System.out.println("capheight: " + convertTTFUnit2PDFUnit(capHeight));
-
- int italic = (int)(italicAngle >> 16);
- System.out.println("Italic: " + italic);
- System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000));
- if ((italicAngle % 0x10000) > 0) {
- System.out.print("."
- + (short)((italicAngle % 0x10000) * 1000)
- / 0x10000);
- }
- System.out.println();
- System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender));
- System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender));
- System.out.println("FontBBox: [" + convertTTFUnit2PDFUnit(fontBBox1)
- + " " + convertTTFUnit2PDFUnit(fontBBox2) + " "
- + convertTTFUnit2PDFUnit(fontBBox3) + " "
- + convertTTFUnit2PDFUnit(fontBBox4) + "]");
- }
-
- private String formatUnitsForDebug(int units) {
- return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units";
- }
-
- /**
- * Map a glyph index to the corresponding unicode code point
- *
- * @param glyphIndex
- * @return unicode code point
- */
- private Integer glyphToUnicode(int glyphIndex) {
- return glyphToUnicodeMap.get(new Integer(glyphIndex));
- }
-
- /**
- * Map a unicode code point to the corresponding glyph index
- *
- * @param unicodeIndex unicode code point
- * @return glyph index
- */
- private Integer unicodeToGlyph(int unicodeIndex) throws IOException {
- final Integer result
- = unicodeToGlyphMap.get(new Integer(unicodeIndex));
- if (result == null) {
- throw new IOException(
- "Glyph index not found for unicode value " + unicodeIndex);
- }
- return result;
- }
-
- String getGlyphName(int glyphIndex) {
- return mtxTab[glyphIndex].getName();
- }
-
- /**
- * Determine if advanced (typographic) table is present.
- * @return true if advanced (typographic) table is present
- */
- public boolean hasAdvancedTable() {
- if (advancedTableReader != null) {
- return advancedTableReader.hasAdvancedTable();
- } else {
- return false;
- }
}
- /**
- * Returns the GDEF table or null if none present.
- * @return the GDEF table
- */
- public GlyphDefinitionTable getGDEF() {
- if (advancedTableReader != null) {
- return advancedTableReader.getGDEF();
- } else {
- return null;
- }
+ @Override
+ protected void updateBBoxAndOffset() throws IOException {
+ readIndexToLocation();
+ readGlyf();
}
/**
- * Returns the GSUB table or null if none present.
- * @return the GSUB table
+ * Read the "loca" table.
+ * @throws IOException In case of a I/O problem
*/
- public GlyphSubstitutionTable getGSUB() {
- if (advancedTableReader != null) {
- return advancedTableReader.getGSUB();
- } else {
- return null;
+ protected final void readIndexToLocation()
+ throws IOException {
+ if (!seekTab(fontFile, OFTableName.LOCA, 0)) {
+ throw new IOException("'loca' table not found, happens when the font file doesn't"
+ + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)");
}
- }
-
- /**
- * Returns the GPOS table or null if none present.
- * @return the GPOS table
- */
- public GlyphPositioningTable getGPOS() {
- if (advancedTableReader != null) {
- return advancedTableReader.getGPOS();
- } else {
- return null;
+ for (int i = 0; i < numberOfGlyphs; i++) {
+ mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong()
+ : (fontFile.readTTFUShort() << 1));
}
+ lastLoca = (locaFormat == 1 ? fontFile.readTTFULong()
+ : (fontFile.readTTFUShort() << 1));
}
- /**
- * Static main method to get info about a TrueType font.
- * @param args The command line arguments
- */
- public static void main(String[] args) {
- InputStream stream = null;
- try {
- boolean useKerning = true;
- boolean useAdvanced = true;
- TTFFile ttfFile = new TTFFile(useKerning, useAdvanced);
-
- stream = new FileInputStream(args[0]);
- FontFileReader reader = new FontFileReader(stream);
-
- String name = null;
- if (args.length >= 2) {
- name = args[1];
- }
-
- ttfFile.readFont(reader, name);
- ttfFile.printStuff();
-
- } catch (IOException ioe) {
- System.err.println("Problem reading font: " + ioe.toString());
- ioe.printStackTrace(System.err);
- } finally {
- IOUtils.closeQuietly(stream);
- }
+ @Override
+ protected void initializeFont(FontFileReader in) throws IOException {
+ fontFile = in;
}
}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
deleted file mode 100644
index b7adbd4c9..000000000
--- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * 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.truetype;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.io.IOUtils;
-
-import org.apache.fop.apps.io.InternalResourceResolver;
-import org.apache.fop.fonts.CIDFontType;
-import org.apache.fop.fonts.CMapSegment;
-import org.apache.fop.fonts.EmbeddingMode;
-import org.apache.fop.fonts.EncodingMode;
-import org.apache.fop.fonts.FontLoader;
-import org.apache.fop.fonts.FontType;
-import org.apache.fop.fonts.MultiByteFont;
-import org.apache.fop.fonts.NamedCharacter;
-import org.apache.fop.fonts.SingleByteFont;
-import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;
-import org.apache.fop.util.HexEncoder;
-
-/**
- * Loads a TrueType font into memory directly from the original font file.
- */
-public class TTFFontLoader extends FontLoader {
-
- private MultiByteFont multiFont;
- private SingleByteFont singleFont;
- private final String subFontName;
- private EncodingMode encodingMode;
- private EmbeddingMode embeddingMode;
-
- /**
- * Default constructor
- * @param fontFileURI the URI representing the font file
- * @param resourceResolver the resource resolver for font URI resolution
- */
- public TTFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) {
- this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resourceResolver);
- }
-
- /**
- * Additional constructor for TrueType Collections.
- * @param fontFileURI the URI representing the font file
- * @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal
- * TrueType fonts)
- * @param embedded indicates whether the font is embedded or referenced
- * @param embeddingMode the embedding mode of the font
- * @param encodingMode the requested encoding mode
- * @param useKerning true to enable loading kerning info if available, false to disable
- * @param useAdvanced true to enable loading advanced info if available, false to disable
- * @param resolver the FontResolver for font URI resolution
- */
- public TTFFontLoader(URI fontFileURI, String subFontName, boolean embedded,
- EmbeddingMode embeddingMode, EncodingMode encodingMode, boolean useKerning,
- boolean useAdvanced, InternalResourceResolver resolver) {
- super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
- this.subFontName = subFontName;
- this.encodingMode = encodingMode;
- this.embeddingMode = embeddingMode;
- if (this.encodingMode == EncodingMode.AUTO) {
- this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType
- }
- if (this.embeddingMode == EmbeddingMode.AUTO) {
- this.embeddingMode = EmbeddingMode.SUBSET;
- }
- }
-
- /** {@inheritDoc} */
- protected void read() throws IOException {
- read(this.subFontName);
- }
-
- /**
- * Reads a TrueType font.
- * @param ttcFontName the TrueType sub-font name of TrueType Collection (may be null for
- * normal TrueType fonts)
- * @throws IOException if an I/O error occurs
- */
- private void read(String ttcFontName) throws IOException {
- InputStream in = resourceResolver.getResource(this.fontFileURI);
- try {
- TTFFile ttf = new TTFFile(useKerning, useAdvanced);
- FontFileReader reader = new FontFileReader(in);
- boolean supported = ttf.readFont(reader, ttcFontName);
- if (!supported) {
- throw new IOException("TrueType font is not supported: " + fontFileURI);
- }
- buildFont(ttf, ttcFontName);
- loaded = true;
- } finally {
- IOUtils.closeQuietly(in);
- }
- }
-
-
- private void buildFont(TTFFile ttf, String ttcFontName) {
- if (ttf.isCFF()) {
- throw new UnsupportedOperationException(
- "OpenType fonts with CFF data are not supported, yet");
- }
-
- boolean isCid = this.embedded;
- if (this.encodingMode == EncodingMode.SINGLE_BYTE) {
- isCid = false;
- }
-
- if (isCid) {
- multiFont = new MultiByteFont(resourceResolver, embeddingMode);
- returnFont = multiFont;
- multiFont.setTTCName(ttcFontName);
- } else {
- singleFont = new SingleByteFont(resourceResolver);
- returnFont = singleFont;
- }
-
- returnFont.setFontName(ttf.getPostScriptName());
- returnFont.setFullName(ttf.getFullName());
- returnFont.setFamilyNames(ttf.getFamilyNames());
- returnFont.setFontSubFamilyName(ttf.getSubFamilyName());
- returnFont.setCapHeight(ttf.getCapHeight());
- returnFont.setXHeight(ttf.getXHeight());
- returnFont.setAscender(ttf.getLowerCaseAscent());
- returnFont.setDescender(ttf.getLowerCaseDescent());
- returnFont.setFontBBox(ttf.getFontBBox());
- returnFont.setFlags(ttf.getFlags());
- returnFont.setStemV(Integer.parseInt(ttf.getStemV())); //not used for TTF
- returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle()));
- returnFont.setMissingWidth(0);
- returnFont.setWeight(ttf.getWeightClass());
- returnFont.setEmbeddingMode(this.embeddingMode);
- if (isCid) {
- multiFont.setCIDType(CIDFontType.CIDTYPE2);
- int[] wx = ttf.getWidths();
- multiFont.setWidthArray(wx);
- } else {
- singleFont.setFontType(FontType.TRUETYPE);
- singleFont.setEncoding(ttf.getCharSetName());
- returnFont.setFirstChar(ttf.getFirstChar());
- returnFont.setLastChar(ttf.getLastChar());
- singleFont.setTrueTypePostScriptVersion(ttf.getPostScriptVersion());
- copyWidthsSingleByte(ttf);
- }
- returnFont.setCMap(getCMap(ttf));
-
- if (useKerning) {
- copyKerning(ttf, isCid);
- }
- if (useAdvanced) {
- copyAdvanced(ttf);
- }
- if (this.embedded) {
- if (ttf.isEmbeddable()) {
- returnFont.setEmbedURI(this.fontFileURI);
- } else {
- String msg = "The font " + this.fontFileURI + " is not embeddable due to a"
- + " licensing restriction.";
- throw new RuntimeException(msg);
- }
- }
- }
-
- private CMapSegment[] getCMap(TTFFile ttf) {
- CMapSegment[] array = new CMapSegment[ttf.getCMaps().size()];
- return ttf.getCMaps().toArray(array);
- }
-
- private void copyWidthsSingleByte(TTFFile ttf) {
- int[] wx = ttf.getWidths();
- for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
- singleFont.setWidth(i, ttf.getCharWidth(i));
- }
-
- for (CMapSegment segment : ttf.getCMaps()) {
- if (segment.getUnicodeStart() < 0xFFFE) {
- for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) {
- int codePoint = singleFont.getEncoding().mapChar(u);
- if (codePoint <= 0) {
- int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart();
- String glyphName = ttf.getGlyphName(glyphIndex);
- if (glyphName.length() == 0 && ttf.getPostScriptVersion() != PostScriptVersion.V2) {
- glyphName = "u" + HexEncoder.encode(u);
- }
- if (glyphName.length() > 0) {
- String unicode = Character.toString(u);
- NamedCharacter nc = new NamedCharacter(glyphName, unicode);
- singleFont.addUnencodedCharacter(nc, wx[glyphIndex]);
- }
- }
- }
- }
- }
- }
-
- /**
- * Copy kerning information.
- */
- private void copyKerning(TTFFile ttf, boolean isCid) {
-
- // Get kerning
- Set kerningSet;
- if (isCid) {
- kerningSet = ttf.getKerning().keySet();
- } else {
- kerningSet = ttf.getAnsiKerning().keySet();
- }
-
- for (Integer kpx1 : kerningSet) {
- Map h2;
- if (isCid) {
- h2 = ttf.getKerning().get(kpx1);
- } else {
- h2 = ttf.getAnsiKerning().get(kpx1);
- }
- returnFont.putKerningEntry(kpx1, h2);
- }
- }
-
- /**
- * Copy advanced typographic information.
- */
- private void copyAdvanced(TTFFile ttf) {
- if (returnFont instanceof MultiByteFont) {
- MultiByteFont mbf = (MultiByteFont) returnFont;
- mbf.setGDEF(ttf.getGDEF());
- mbf.setGSUB(ttf.getGSUB());
- mbf.setGPOS(ttf.getGPOS());
- }
- }
-
-}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java
deleted file mode 100644
index 6884a633d..000000000
--- a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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.truetype;
-
-import java.util.List;
-
-/**
- * This class represents a TrueType Mtx Entry.
- */
-class TTFMtxEntry {
-
- private int wx;
- private int lsb;
- private String name = "";
- private int index;
- private List unicodeIndex = new java.util.ArrayList();
- private int[] boundingBox = new int[4];
- private long offset;
- private byte found = 0;
-
- /**
- * Returns a String representation of this object.
- *
- * @param t TTFFile to use for unit conversion
- * @return String String representation
- */
- public String toString(TTFFile t) {
- return "Glyph " + name + " index: " + getIndexAsString() + " bbox ["
- + t.convertTTFUnit2PDFUnit(boundingBox[0]) + " "
- + t.convertTTFUnit2PDFUnit(boundingBox[1]) + " "
- + t.convertTTFUnit2PDFUnit(boundingBox[2]) + " "
- + t.convertTTFUnit2PDFUnit(boundingBox[3]) + "] wx: "
- + t.convertTTFUnit2PDFUnit(wx);
- }
-
- /**
- * Returns the boundingBox.
- * @return int[]
- */
- public int[] getBoundingBox() {
- return boundingBox;
- }
-
- /**
- * Sets the boundingBox.
- * @param boundingBox The boundingBox to set
- */
- public void setBoundingBox(int[] boundingBox) {
- this.boundingBox = boundingBox;
- }
-
- /**
- * Returns the found.
- * @return byte
- */
- public byte getFound() {
- return found;
- }
-
- /**
- * Returns the index.
- * @return int
- */
- public int getIndex() {
- return index;
- }
-
- /**
- * Determines whether this index represents a reserved character.
- * @return True if it is reserved
- */
- public boolean isIndexReserved() {
- return (getIndex() >= 32768) && (getIndex() <= 65535);
- }
-
- /**
- * Returns a String representation of the index taking into account if
- * the index is in the reserved range.
- * @return index as String
- */
- public String getIndexAsString() {
- if (isIndexReserved()) {
- return Integer.toString(getIndex()) + " (reserved)";
- } else {
- return Integer.toString(getIndex());
- }
- }
-
- /**
- * Returns the lsb.
- * @return int
- */
- public int getLsb() {
- return lsb;
- }
-
- /**
- * Returns the name.
- * @return String
- */
- public String getName() {
- return name;
- }
-
- /**
- * Returns the offset.
- * @return long
- */
- public long getOffset() {
- return offset;
- }
-
- /**
- * Returns the unicodeIndex.
- * @return List
- */
- public List getUnicodeIndex() {
- return unicodeIndex;
- }
-
- /**
- * Returns the wx.
- * @return int
- */
- public int getWx() {
- return wx;
- }
-
- /**
- * Sets the found.
- * @param found The found to set
- */
- public void setFound(byte found) {
- this.found = found;
- }
-
- /**
- * Sets the index.
- * @param index The index to set
- */
- public void setIndex(int index) {
- this.index = index;
- }
-
- /**
- * Sets the lsb.
- * @param lsb The lsb to set
- */
- public void setLsb(int lsb) {
- this.lsb = lsb;
- }
-
- /**
- * Sets the name.
- * @param name The name to set
- */
- public void setName(String name) {
- this.name = name;
- }
-
- /**
- * Sets the offset.
- * @param offset The offset to set
- */
- public void setOffset(long offset) {
- this.offset = offset;
- }
-
- /**
- * Sets the wx.
- * @param wx The wx to set
- */
- public void setWx(int wx) {
- this.wx = wx;
- }
-
-
-}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
index c9a0e6c8e..ff46af1c7 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
@@ -43,7 +43,7 @@ public class TTFSubSetFile extends TTFFile {
* Offsets in name table to be filled out by table.
* The offsets are to the checkSum field
*/
- private Map offsets = new HashMap();
+ private Map offsets = new HashMap();
private int checkSumAdjustmentOffset = 0;
private int locaOffset = 0;
@@ -67,8 +67,8 @@ public class TTFSubSetFile extends TTFFile {
}
/** The dir tab entries in the new subset font. */
- private Map newDirTabs
- = new HashMap();
+ private Map newDirTabs
+ = new HashMap();
private int determineTableCount() {
int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp
@@ -117,29 +117,29 @@ public class TTFSubSetFile extends TTFFile {
writeUShort((numTables * 16) - searchRange);
realSize += 2;
// Create space for the table entries (these must be in ASCII alphabetical order[A-Z] then[a-z])
- writeTableName(TTFTableName.OS2);
+ writeTableName(OFTableName.OS2);
if (hasCvt()) {
- writeTableName(TTFTableName.CVT);
+ writeTableName(OFTableName.CVT);
}
if (hasFpgm()) {
- writeTableName(TTFTableName.FPGM);
+ writeTableName(OFTableName.FPGM);
}
- writeTableName(TTFTableName.GLYF);
- writeTableName(TTFTableName.HEAD);
- writeTableName(TTFTableName.HHEA);
- writeTableName(TTFTableName.HMTX);
- writeTableName(TTFTableName.LOCA);
- writeTableName(TTFTableName.MAXP);
- writeTableName(TTFTableName.NAME);
- writeTableName(TTFTableName.POST);
+ writeTableName(OFTableName.GLYF);
+ writeTableName(OFTableName.HEAD);
+ writeTableName(OFTableName.HHEA);
+ writeTableName(OFTableName.HMTX);
+ writeTableName(OFTableName.LOCA);
+ writeTableName(OFTableName.MAXP);
+ writeTableName(OFTableName.NAME);
+ writeTableName(OFTableName.POST);
if (hasPrep()) {
- writeTableName(TTFTableName.PREP);
+ writeTableName(OFTableName.PREP);
}
- newDirTabs.put(TTFTableName.TABLE_DIRECTORY, new TTFDirTabEntry(0, currentPos));
+ newDirTabs.put(OFTableName.TABLE_DIRECTORY, new OFDirTabEntry(0, currentPos));
}
- private void writeTableName(TTFTableName tableName) {
+ private void writeTableName(OFTableName tableName) {
writeString(tableName.getName());
offsets.put(tableName, currentPos);
currentPos += 12;
@@ -148,15 +148,15 @@ public class TTFSubSetFile extends TTFFile {
private boolean hasCvt() {
- return dirTabs.containsKey(TTFTableName.CVT);
+ return dirTabs.containsKey(OFTableName.CVT);
}
private boolean hasFpgm() {
- return dirTabs.containsKey(TTFTableName.FPGM);
+ return dirTabs.containsKey(OFTableName.FPGM);
}
private boolean hasPrep() {
- return dirTabs.containsKey(TTFTableName.PREP);
+ return dirTabs.containsKey(OFTableName.PREP);
}
/**
@@ -165,15 +165,15 @@ public class TTFSubSetFile extends TTFFile {
private void createLoca(int size) throws IOException {
pad4();
locaOffset = currentPos;
- int dirTableOffset = offsets.get(TTFTableName.LOCA);
+ int dirTableOffset = offsets.get(OFTableName.LOCA);
writeULong(dirTableOffset + 4, currentPos);
writeULong(dirTableOffset + 8, size * 4 + 4);
currentPos += size * 4 + 4;
realSize += size * 4 + 4;
}
- private boolean copyTable(FontFileReader in, TTFTableName tableName) throws IOException {
- TTFDirTabEntry entry = dirTabs.get(tableName);
+ private boolean copyTable(FontFileReader in, OFTableName tableName) throws IOException {
+ OFDirTabEntry entry = dirTabs.get(tableName);
if (entry != null) {
pad4();
seekTab(in, tableName, 0);
@@ -193,28 +193,28 @@ public class TTFSubSetFile extends TTFFile {
* Copy the cvt table as is from original font to subset font
*/
private boolean createCvt(FontFileReader in) throws IOException {
- return copyTable(in, TTFTableName.CVT);
+ return copyTable(in, OFTableName.CVT);
}
/**
* Copy the fpgm table as is from original font to subset font
*/
private boolean createFpgm(FontFileReader in) throws IOException {
- return copyTable(in, TTFTableName.FPGM);
+ return copyTable(in, OFTableName.FPGM);
}
/**
* Copy the name table as is from the original.
*/
private boolean createName(FontFileReader in) throws IOException {
- return copyTable(in, TTFTableName.NAME);
+ return copyTable(in, OFTableName.NAME);
}
/**
* Copy the OS/2 table as is from the original.
*/
private boolean createOS2(FontFileReader in) throws IOException {
- return copyTable(in, TTFTableName.OS2);
+ return copyTable(in, OFTableName.OS2);
}
/**
@@ -222,8 +222,8 @@ public class TTFSubSetFile extends TTFFile {
* and set num glyphs to size
*/
private void createMaxp(FontFileReader in, int size) throws IOException {
- TTFTableName maxp = TTFTableName.MAXP;
- TTFDirTabEntry entry = dirTabs.get(maxp);
+ OFTableName maxp = OFTableName.MAXP;
+ OFDirTabEntry entry = dirTabs.get(maxp);
if (entry != null) {
pad4();
seekTab(in, maxp, 0);
@@ -240,8 +240,8 @@ public class TTFSubSetFile extends TTFFile {
}
private void createPost(FontFileReader in) throws IOException {
- TTFTableName post = TTFTableName.POST;
- TTFDirTabEntry entry = dirTabs.get(post);
+ OFTableName post = OFTableName.POST;
+ OFDirTabEntry entry = dirTabs.get(post);
if (entry != null) {
pad4();
seekTab(in, post, 0);
@@ -266,7 +266,7 @@ public class TTFSubSetFile extends TTFFile {
* Copy the prep table as is from original font to subset font
*/
private boolean createPrep(FontFileReader in) throws IOException {
- return copyTable(in, TTFTableName.PREP);
+ return copyTable(in, OFTableName.PREP);
}
@@ -275,15 +275,15 @@ public class TTFSubSetFile extends TTFFile {
* and fill in size of hmtx table
*/
private void createHhea(FontFileReader in, int size) throws IOException {
- TTFDirTabEntry entry = dirTabs.get(TTFTableName.HHEA);
+ OFDirTabEntry entry = dirTabs.get(OFTableName.HHEA);
if (entry != null) {
pad4();
- seekTab(in, TTFTableName.HHEA, 0);
+ seekTab(in, OFTableName.HHEA, 0);
System.arraycopy(in.getBytes((int) entry.getOffset(), (int) entry.getLength()), 0,
output, currentPos, (int) entry.getLength());
writeUShort((int) entry.getLength() + currentPos - 2, size);
- updateCheckSum(currentPos, (int) entry.getLength(), TTFTableName.HHEA);
+ updateCheckSum(currentPos, (int) entry.getLength(), OFTableName.HHEA);
currentPos += (int) entry.getLength();
realSize += (int) entry.getLength();
} else {
@@ -299,8 +299,8 @@ public class TTFSubSetFile extends TTFFile {
* in checkSumAdjustmentOffset
*/
private void createHead(FontFileReader in) throws IOException {
- TTFTableName head = TTFTableName.HEAD;
- TTFDirTabEntry entry = dirTabs.get(head);
+ OFTableName head = OFTableName.HEAD;
+ OFDirTabEntry entry = dirTabs.get(head);
if (entry != null) {
pad4();
seekTab(in, head, 0);
@@ -329,8 +329,8 @@ public class TTFSubSetFile extends TTFFile {
*/
private void createGlyf(FontFileReader in,
Map glyphs) throws IOException {
- TTFTableName glyf = TTFTableName.GLYF;
- TTFDirTabEntry entry = dirTabs.get(glyf);
+ OFTableName glyf = OFTableName.GLYF;
+ OFDirTabEntry entry = dirTabs.get(glyf);
int size = 0;
int startPos = 0;
int endOffset = 0; // Store this as the last loca
@@ -393,10 +393,10 @@ public class TTFSubSetFile extends TTFFile {
writeULong(locaOffset + glyphs.size() * 4, endOffset);
int locaSize = glyphs.size() * 4 + 4;
int checksum = getCheckSum(output, locaOffset, locaSize);
- writeULong(offsets.get(TTFTableName.LOCA), checksum);
+ writeULong(offsets.get(OFTableName.LOCA), checksum);
int padSize = (locaOffset + locaSize) % 4;
- newDirTabs.put(TTFTableName.LOCA,
- new TTFDirTabEntry(locaOffset, locaSize + padSize));
+ newDirTabs.put(OFTableName.LOCA,
+ new OFDirTabEntry(locaOffset, locaSize + padSize));
} else {
throw new IOException("Can't find glyf table");
}
@@ -420,8 +420,8 @@ public class TTFSubSetFile extends TTFFile {
*/
private void createHmtx(FontFileReader in,
Map glyphs) throws IOException {
- TTFTableName hmtx = TTFTableName.HMTX;
- TTFDirTabEntry entry = dirTabs.get(hmtx);
+ OFTableName hmtx = OFTableName.HMTX;
+ OFDirTabEntry entry = dirTabs.get(hmtx);
int longHorMetricSize = glyphs.size() * 2;
int leftSideBearingSize = glyphs.size() * 2;
@@ -457,11 +457,11 @@ public class TTFSubSetFile extends TTFFile {
* new index as (Integer) value)
* @throws IOException in case of an I/O problem
*/
- public void readFont(FontFileReader in, String name,
+ public void readFont(FontFileReader in, String name, String header,
Map glyphs) throws IOException {
fontFile = in;
//Check if TrueType collection, and that the name exists in the collection
- if (!checkTTC(name)) {
+ if (!checkTTC(header, name)) {
throw new IOException("Failed to read font");
}
@@ -533,7 +533,7 @@ public class TTFSubSetFile extends TTFFile {
glyphOffsets[i + 1] - glyphOffsets[i]);
}
// Stream the last glyph
- TTFDirTabEntry glyf = newDirTabs.get(TTFTableName.GLYF);
+ OFDirTabEntry glyf = newDirTabs.get(OFTableName.GLYF);
long lastGlyphLength = glyf.getLength()
- (glyphOffsets[glyphOffsets.length - 1] - glyf.getOffset());
glyphOut.streamGlyph(output, glyphOffsets[glyphOffsets.length - 1],
@@ -543,14 +543,14 @@ public class TTFSubSetFile extends TTFFile {
@Override
public void stream(TTFOutputStream ttfOut) throws IOException {
- SortedSet> sortedDirTabs
+ SortedSet> sortedDirTabs
= sortDirTabMap(newDirTabs);
TTFTableOutputStream tableOut = ttfOut.getTableOutputStream();
TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream();
ttfOut.startFontStream();
- for (Map.Entry entry : sortedDirTabs) {
- if (entry.getKey().equals(TTFTableName.GLYF)) {
+ for (Map.Entry entry : sortedDirTabs) {
+ if (entry.getKey().equals(OFTableName.GLYF)) {
handleGlyphSubset(glyphOut);
} else {
tableOut.streamTable(output, (int) entry.getValue().getOffset(),
@@ -562,7 +562,7 @@ public class TTFSubSetFile extends TTFFile {
private void scanGlyphs(FontFileReader in, Map subsetGlyphs)
throws IOException {
- TTFDirTabEntry glyfTableInfo = dirTabs.get(TTFTableName.GLYF);
+ OFDirTabEntry glyfTableInfo = dirTabs.get(OFTableName.GLYF);
if (glyfTableInfo == null) {
throw new IOException("Glyf table could not be found");
}
@@ -663,11 +663,11 @@ public class TTFSubSetFile extends TTFFile {
}
- private void updateCheckSum(int tableStart, int tableSize, TTFTableName tableName) {
+ private void updateCheckSum(int tableStart, int tableSize, OFTableName tableName) {
int checksum = getCheckSum(output, tableStart, tableSize);
int offset = offsets.get(tableName);
int padSize = getPadSize(tableStart + tableSize);
- newDirTabs.put(tableName, new TTFDirTabEntry(tableStart, tableSize + padSize));
+ newDirTabs.put(tableName, new OFDirTabEntry(tableStart, tableSize + padSize));
writeULong(offset, checksum);
writeULong(offset + 4, tableStart);
writeULong(offset + 8, tableSize);
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java b/src/java/org/apache/fop/fonts/truetype/TTFTableName.java
deleted file mode 100644
index e5ad63128..000000000
--- a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.truetype;
-
-
-/**
- * Represents table names as found in a TrueType font's Table Directory.
- * TrueType fonts may have custom tables so we cannot use an enum.
- */
-public final class TTFTableName {
-
- /** The first table in a TrueType font file containing metadata about other tables. */
- public static final TTFTableName TABLE_DIRECTORY = new TTFTableName("tableDirectory");
-
- /** Embedded bitmap data. */
- public static final TTFTableName EBDT = new TTFTableName("EBDT");
-
- /** Embedded bitmap location data. */
- public static final TTFTableName EBLC = new TTFTableName("EBLC");
-
- /** Embedded bitmap scaling data. */
- public static final TTFTableName EBSC = new TTFTableName("EBSC");
-
- /** A FontForge specific table. */
- public static final TTFTableName FFTM = new TTFTableName("FFTM");
-
- /** Divides glyphs into various classes that make using the GPOS/GSUB tables easier. */
- public static final TTFTableName GDEF = new TTFTableName("GDEF");
-
- /** Provides kerning information, mark-to-base, etc. for opentype fonts. */
- public static final TTFTableName GPOS = new TTFTableName("GPOS");
-
- /** Provides ligature information, swash, etc. for opentype fonts. */
- public static final TTFTableName GSUB = new TTFTableName("GSUB");
-
- /** Linear threshold table. */
- public static final TTFTableName LTSH = new TTFTableName("LTSH");
-
- /** OS/2 and Windows specific metrics. */
- public static final TTFTableName OS2 = new TTFTableName("OS/2");
-
- /** PCL 5 data. */
- public static final TTFTableName PCLT = new TTFTableName("PCLT");
-
- /** Vertical Device Metrics table. */
- public static final TTFTableName VDMX = new TTFTableName("VDMX");
-
- /** Character to glyph mapping. */
- public static final TTFTableName CMAP = new TTFTableName("cmap");
-
- /** Control Value Table. */
- public static final TTFTableName CVT = new TTFTableName("cvt ");
-
- /** Font program. */
- public static final TTFTableName FPGM = new TTFTableName("fpgm");
-
- /** Grid-fitting and scan conversion procedure (grayscale). */
- public static final TTFTableName GASP = new TTFTableName("gasp");
-
- /** Glyph data. */
- public static final TTFTableName GLYF = new TTFTableName("glyf");
-
- /** Horizontal device metrics. */
- public static final TTFTableName HDMX = new TTFTableName("hdmx");
-
- /** Font header. */
- public static final TTFTableName HEAD = new TTFTableName("head");
-
- /** Horizontal header. */
- public static final TTFTableName HHEA = new TTFTableName("hhea");
-
- /** Horizontal metrics. */
- public static final TTFTableName HMTX = new TTFTableName("hmtx");
-
- /** Kerning. */
- public static final TTFTableName KERN = new TTFTableName("kern");
-
- /** Index to location. */
- public static final TTFTableName LOCA = new TTFTableName("loca");
-
- /** Maximum profile. */
- public static final TTFTableName MAXP = new TTFTableName("maxp");
-
- /** Naming table. */
- public static final TTFTableName NAME = new TTFTableName("name");
-
- /** PostScript information. */
- public static final TTFTableName POST = new TTFTableName("post");
-
- /** CVT Program. */
- public static final TTFTableName PREP = new TTFTableName("prep");
-
- /** Vertical Metrics header. */
- public static final TTFTableName VHEA = new TTFTableName("vhea");
-
- /** Vertical Metrics. */
- public static final TTFTableName VMTX = new TTFTableName("vmtx");
-
- private final String name;
-
- private TTFTableName(String name) {
- this.name = name;
- }
-
- /**
- * Returns the name of the table as it should be in the Directory Table.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Returns an instance of this class corresponding to the given string representation.
- * @param tableName table name as in the Table Directory
- * @return TTFTableName
- */
- public static TTFTableName getValue(String tableName) {
- if (tableName != null) {
- return new TTFTableName(tableName);
- }
- throw new IllegalArgumentException("A TrueType font table name must not be null");
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof TTFTableName)) {
- return false;
- }
- TTFTableName to = (TTFTableName) o;
- return this.name.equals(to.getName());
- }
-
- @Override
- public String toString() {
- return name;
- }
-
-}
diff --git a/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java b/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java
new file mode 100644
index 000000000..53f0b36b4
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java
@@ -0,0 +1,74 @@
+/*
+ * 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.pdf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * PDFStream for embeddable OpenType CFF fonts.
+ */
+public class PDFCFFStreamType0C extends AbstractPDFFontStream {
+
+ private byte[] cffData;
+ private boolean fullEmbed;
+
+ /**
+ * Main constructor
+ * @param fullEmbed Determines whether the font is fully embedded
+ */
+ public PDFCFFStreamType0C(boolean fullEmbed) {
+ super();
+ this.fullEmbed = fullEmbed;
+ }
+
+ protected int getSizeHint() throws IOException {
+ if (this.cffData != null) {
+ return cffData.length;
+ } else {
+ return 0; //no hint available
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void outputRawStreamData(OutputStream out) throws IOException {
+ out.write(this.cffData);
+ }
+
+ /** {@inheritDoc} */
+ protected void populateStreamDict(Object lengthEntry) {
+ String type = (fullEmbed) ? "OpenType" : "CIDFontType0C";
+ put("Subtype", new PDFName(type));
+ super.populateStreamDict(lengthEntry);
+ }
+
+ /**
+ * Sets the CFF font data.
+ * @param data the font payload
+ * @param size size of the payload
+ * @throws IOException in case of an I/O problem
+ */
+ public void setData(byte[] data, int size) throws IOException {
+ this.cffData = new byte[size];
+ System.arraycopy(data, 0, this.cffData, 0, size);
+ }
+
+}
+
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
index 2fe9f29e0..8fd8d3444 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
@@ -449,14 +449,13 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
private class Rev5Engine extends InitializationEngine {
- // private SecureRandom random = new SecureRandom();
- private byte[] userValidationSalt = new byte[8];
- private byte[] userKeySalt = new byte[8];
- private byte[] ownerValidationSalt = new byte[8];
- private byte[] ownerKeySalt = new byte[8];
- private byte[] ueValue;
- private byte[] oeValue;
- private final boolean encryptMetadata;
+ protected byte[] userValidationSalt = new byte[8];
+ protected byte[] userKeySalt = new byte[8];
+ protected byte[] ownerValidationSalt = new byte[8];
+ protected byte[] ownerKeySalt = new byte[8];
+ protected byte[] ueValue;
+ protected byte[] oeValue;
+ protected final boolean encryptMetadata;
Rev5Engine(EncryptionSettings encryptionSettings) {
super(encryptionSettings);
@@ -563,7 +562,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
/**
* Algorithm 3.8-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
*/
- private void computeUEValue() {
+ protected void computeUEValue() {
digest.reset();
byte[] prepared = preparedUserPassword;
byte[] concatenated = new byte[prepared.length + 8];
@@ -577,7 +576,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
/**
* Algorithm 3.9-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
*/
- private void computeOEValue() {
+ protected void computeOEValue() {
digest.reset();
byte[] prepared = preparedOwnerPassword;
byte[] concatenated = new byte[prepared.length + 56];
@@ -615,6 +614,86 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
}
}
+ private class Rev6Engine extends Rev5Engine {
+
+ private MessageDigest digest384;
+ private MessageDigest digest512;
+
+ Rev6Engine(EncryptionSettings encryptionSettings) {
+ super(encryptionSettings);
+ try {
+ digest384 = MessageDigest.getInstance("SHA-384");
+ digest512 = MessageDigest.getInstance("SHA-512");
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e.getMessage());
+ }
+ }
+
+ @Override
+ protected void computeUValue() {
+ byte[] userBytes = new byte[16];
+ random.nextBytes(userBytes);
+ System.arraycopy(userBytes, 0, userValidationSalt, 0, 8);
+ System.arraycopy(userBytes, 8, userKeySalt, 0, 8);
+ digest.reset();
+ byte[] prepared = preparedUserPassword;
+ byte[] concatenated = new byte[prepared.length + 8];
+ System.arraycopy(prepared, 0, concatenated, 0, prepared.length);
+ System.arraycopy(userValidationSalt, 0, concatenated, prepared.length, 8);
+ digest.update(concatenated);
+ byte[] block = digest.digest();
+ int blockSize = 32;
+ byte[] key = new byte[16];
+ byte[] iv = new byte[16];
+ int length = prepared.length + blockSize;
+ byte[] data = new byte[length * 64];
+ for (int i = 0; i < 64 || i < data[length * 64 - 1] + 32; i++) {
+ System.arraycopy(block, 0, key, 0, 16);
+ System.arraycopy(block, 16, iv, 0, 16);
+ for (int j = 0; j < 64; j++) {
+ System.arraycopy(prepared, 0, data, j * length, prepared.length);
+ System.arraycopy(block, 0, data, j * length + prepared.length, blockSize);
+ }
+ try {
+ final Cipher cipher = PDFEncryptionJCE.initCipher(key, false, iv);
+ data = cipher.doFinal(data);
+ } catch (IllegalBlockSizeException e) {
+ throw new IllegalStateException(e.getMessage());
+ } catch (BadPaddingException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ int sum = 0;
+ for (int k = 0; k < 16; k++) {
+ sum += data[k];
+ }
+ blockSize = 32 + (sum % 3) * 16;
+ switch (blockSize) {
+ case 32:
+ digest.reset();
+ digest.update(data);
+ block = digest.digest();
+ break;
+ case 48:
+ digest384.reset();
+ digest384.update(data);
+ block = digest384.digest();
+ break;
+ case 64:
+ digest512.reset();
+ digest512.update(data);
+ block = digest512.digest();
+ break;
+ default:
+ // not possible
+ break;
+ }
+ length = prepared.length + blockSize;
+ data = new byte[length * 64];
+ }
+ }
+
+ }
+
private class EncryptionFilter extends PDFFilter {
private int streamNumber;
diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java
index 1756f1d56..631499af1 100644
--- a/src/java/org/apache/fop/pdf/PDFFactory.java
+++ b/src/java/org/apache/fop/pdf/PDFFactory.java
@@ -56,6 +56,8 @@ import org.apache.fop.fonts.SingleByteEncoding;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OFFontLoader;
+import org.apache.fop.fonts.truetype.OTFSubSetFile;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.type1.PFBData;
import org.apache.fop.fonts.type1.PFBParser;
@@ -1387,15 +1389,15 @@ public class PDFFactory {
int firstChar = singleByteFont.getFirstChar();
int lastChar = singleByteFont.getLastChar();
nonBase14.setWidthMetrics(firstChar,
- lastChar,
- new PDFArray(null, metrics.getWidths()));
+ lastChar,
+ new PDFArray(null, metrics.getWidths()));
//Handle encoding
SingleByteEncoding mapping = singleByteFont.getEncoding();
if (singleByteFont.isSymbolicFont()) {
//no encoding, use the font's encoding
if (forceToUnicode) {
- generateToUnicodeCmap(nonBase14, mapping);
+ generateToUnicodeCmap(nonBase14, mapping);
}
} else if (PDFEncoding.isPredefinedEncoding(mapping.getName())) {
font.setEncoding(mapping.getName());
@@ -1403,7 +1405,7 @@ public class PDFFactory {
//believed.
} else {
Object pdfEncoding = createPDFEncoding(mapping,
- singleByteFont.getFontName());
+ singleByteFont.getFontName());
if (pdfEncoding instanceof PDFEncoding) {
font.setEncoding((PDFEncoding)pdfEncoding);
} else {
@@ -1518,7 +1520,8 @@ public class PDFFactory {
// Check if the font is embeddable
if (desc.isEmbeddable()) {
- AbstractPDFStream stream = makeFontFile(desc);
+ AbstractPDFStream stream = makeFontFile(desc, fontPrefix);
+
if (stream != null) {
descriptor.setFontFile(desc.getFontType(), stream);
getDocument().registerObject(stream);
@@ -1564,7 +1567,7 @@ public class PDFFactory {
* @param desc FontDescriptor of the font.
* @return PDFStream The embedded font file
*/
- public AbstractPDFStream makeFontFile(FontDescriptor desc) {
+ public AbstractPDFStream makeFontFile(FontDescriptor desc, String fontPrefix) {
if (desc.getFontType() == FontType.OTHER) {
throw new IllegalArgumentException("Trying to embed unsupported font type: "
+ desc.getFontType());
@@ -1578,20 +1581,24 @@ public class PDFFactory {
if (in == null) {
return null;
} else {
- AbstractPDFStream embeddedFont;
+ AbstractPDFStream embeddedFont = null;
if (desc.getFontType() == FontType.TYPE0) {
MultiByteFont mbfont = (MultiByteFont) font;
FontFileReader reader = new FontFileReader(in);
byte[] fontBytes;
+ String header = OFFontLoader.readHeader(reader);
+ boolean isCFF = mbfont.isOTFFile();
if (font.getEmbeddingMode() == EmbeddingMode.FULL) {
fontBytes = reader.getAllBytes();
+ if (isCFF) {
+ //Ensure version 1.6 for full OTF CFF embedding
+ document.setPDFVersion(Version.V1_6);
+ }
} else {
- TTFSubSetFile ttfFile = new TTFSubSetFile();
- ttfFile.readFont(reader, mbfont.getTTCName(), mbfont.getUsedGlyphs());
- fontBytes = ttfFile.getFontSubset();
+ fontBytes = getFontSubsetBytes(reader, mbfont, header, fontPrefix, desc,
+ isCFF);
}
- embeddedFont = new PDFTTFStream(fontBytes.length);
- ((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length);
+ embeddedFont = getFontStream(font, fontBytes, isCFF);
} else if (desc.getFontType() == FontType.TYPE1) {
PFBParser parser = new PFBParser();
PFBData pfb = parser.parsePFB(in);
@@ -1621,6 +1628,32 @@ public class PDFFactory {
}
}
+ private byte[] getFontSubsetBytes(FontFileReader reader, MultiByteFont mbfont, String header,
+ String fontPrefix, FontDescriptor desc, boolean isCFF) throws IOException {
+ if (isCFF) {
+ OTFSubSetFile otfFile = new OTFSubSetFile();
+ otfFile.readFont(reader, fontPrefix + desc.getEmbedFontName(), header, mbfont);
+ return otfFile.getFontSubset();
+ } else {
+ TTFSubSetFile otfFile = new TTFSubSetFile();
+ otfFile.readFont(reader, mbfont.getTTCName(), header, mbfont.getUsedGlyphs());
+ return otfFile.getFontSubset();
+ }
+ }
+
+ private AbstractPDFStream getFontStream(CustomFont font, byte[] fontBytes, boolean isCFF)
+ throws IOException {
+ AbstractPDFStream embeddedFont;
+ if (isCFF) {
+ embeddedFont = new PDFCFFStreamType0C(font.getEmbeddingMode() == EmbeddingMode.FULL);
+ ((PDFCFFStreamType0C) embeddedFont).setData(fontBytes, fontBytes.length);
+ } else {
+ embeddedFont = new PDFTTFStream(fontBytes.length);
+ ((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length);
+ }
+ return embeddedFont;
+ }
+
private CustomFont getCustomFont(FontDescriptor desc) {
Typeface tempFont;
if (desc instanceof LazyFont) {
diff --git a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java
index ec4e99101..73dbebc3f 100644
--- a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java
+++ b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java
@@ -102,6 +102,8 @@ public class PDFFontDescriptor extends PDFDictionary {
public void setFontFile(FontType subtype, AbstractPDFStream fontfile) {
if (subtype == FontType.TYPE1) {
put("FontFile", fontfile);
+ } else if (fontfile instanceof PDFCFFStreamType0C) {
+ put("FontFile3", fontfile);
} else {
put("FontFile2", fontfile);
}
diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java
index e57567b88..c22a3ba28 100644
--- a/src/java/org/apache/fop/render/ps/PSFontUtils.java
+++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java
@@ -29,6 +29,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.cff.CFFStandardString;
import org.apache.xmlgraphics.fonts.Glyphs;
import org.apache.xmlgraphics.ps.DSCConstants;
@@ -50,9 +51,14 @@ import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.SingleByteEncoding;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
+import org.apache.fop.fonts.cff.CFFDataReader;
+import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OFFontLoader;
+import org.apache.fop.fonts.truetype.OTFFile;
+import org.apache.fop.fonts.truetype.OTFSubSetFile;
+import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
import org.apache.fop.fonts.truetype.TTFFile;
-import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;
import org.apache.fop.fonts.truetype.TTFOutputStream;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.render.ps.fonts.PSTTFOutputStream;
@@ -221,6 +227,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes,
PSEventProducer eventProducer) throws IOException {
+ boolean embeddedFont = false;
FontType fontType = tf.getFontType();
PSFontResource fontResource = null;
if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE
@@ -232,52 +239,63 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
CustomFont cf = (CustomFont)tf;
if (isEmbeddable(cf)) {
InputStream in = getInputStreamOnFont(gen, cf);
- if (in == null) {
+ if (in != null) {
+ if (fontType == FontType.TYPE0) {
+ if (((MultiByteFont)tf).isOTFFile()) {
+ checkPostScriptLevel3(gen, eventProducer, "OpenType CFF");
+ embedType2CFF(gen, (MultiByteFont) tf, in);
+ } else {
+ if (gen.embedIdentityH()) {
+ checkPostScriptLevel3(gen, eventProducer, "TrueType");
+ /*
+ * First CID-keyed font to be embedded; add
+ * %%IncludeResource: comment for ProcSet CIDInit.
+ */
+ gen.includeProcsetCIDInitResource();
+ }
+ PSResource cidFontResource;
+ cidFontResource = embedType2CIDFont(gen,
+ (MultiByteFont) tf, in);
+ fontResource = PSFontResource.createFontResource(fontRes,
+ gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(),
+ cidFontResource);
+ }
+ }
+ gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes);
+ if (fontType == FontType.TYPE1) {
+ embedType1Font(gen, in);
+ fontResource = PSFontResource.createFontResource(fontRes);
+ } else if (fontType == FontType.TRUETYPE) {
+ embedTrueTypeFont(gen, (SingleByteFont) tf, in);
+ fontResource = PSFontResource.createFontResource(fontRes);
+ } else {
+ composeType0Font(gen, (MultiByteFont) tf, in);
+ }
+ gen.writeDSCComment(DSCConstants.END_RESOURCE);
+ gen.getResourceTracker().registerSuppliedResource(fontRes);
+ embeddedFont = true;
+ } else {
gen.commentln("%WARNING: Could not embed font: " + cf.getEmbedFontName());
log.warn("Font " + cf.getEmbedFontName() + " is marked as supplied in the"
+ " PostScript file but could not be embedded!");
- gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
- fontResource = PSFontResource.createFontResource(fontRes);
- return fontResource;
- }
- if (fontType == FontType.TYPE0) {
- if (gen.embedIdentityH()) {
- checkPostScriptLevel3(gen, eventProducer);
- /*
- * First CID-keyed font to be embedded; add
- * %%IncludeResource: comment for ProcSet CIDInit.
- */
- gen.includeProcsetCIDInitResource();
- }
- PSResource cidFontResource = embedType2CIDFont(gen,
- (MultiByteFont) tf, in);
- fontResource = PSFontResource.createFontResource(fontRes,
- gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(),
- cidFontResource);
}
- gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes);
- if (fontType == FontType.TYPE1) {
- embedType1Font(gen, in);
- fontResource = PSFontResource.createFontResource(fontRes);
- } else if (fontType == FontType.TRUETYPE) {
- embedTrueTypeFont(gen, (SingleByteFont) tf, in);
- fontResource = PSFontResource.createFontResource(fontRes);
- } else {
- composeType0Font(gen, (MultiByteFont) tf, in);
- }
- gen.writeDSCComment(DSCConstants.END_RESOURCE);
- gen.getResourceTracker().registerSuppliedResource(fontRes);
+ }
+ if (!embeddedFont) {
+ gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
+ fontResource = PSFontResource.createFontResource(fontRes);
+ return fontResource;
}
return fontResource;
}
- private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer) {
+ private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer,
+ String fontType) {
if (gen.getPSLevel() < 3) {
if (eventProducer != null) {
eventProducer.postscriptLevel3Needed(gen);
} else {
throw new IllegalStateException("PostScript Level 3 is"
- + " required to use TrueType fonts,"
+ + " required to use " + fontType + " fonts,"
+ " configured level is "
+ gen.getPSLevel());
}
@@ -415,6 +433,96 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
gen.writeln("] composefont pop");
}
+ private static void embedType2CFF(PSGenerator gen,
+ MultiByteFont font, InputStream fontStream) throws IOException {
+ FontFileReader reader = new FontFileReader(fontStream);
+ String header = OFFontLoader.readHeader(reader);
+ String psName;
+ CFFDataReader cffReader = new CFFDataReader(reader);
+ if (cffReader.getFDSelect() != null) {
+ throw new UnsupportedOperationException("CID-Keyed OTF CFF fonts are not supported"
+ + " for PostScript output.");
+ }
+
+ byte[] bytes;
+ if (font.getEmbeddingMode() == EmbeddingMode.FULL) {
+ font.setFontName(new String(cffReader.getNameIndex().getValue(0)));
+ psName = font.getEmbedFontName();
+ Map topDICT = cffReader.getTopDictEntries();
+ int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue();
+ for (int gid = 0; gid < cffReader.getCharStringIndex().getNumObjects(); gid++) {
+ int sid = cffReader.getSIDFromGID(charsetOffset, gid);
+
+ //Check whether the SID falls into the standard string set
+ if (sid < 391) {
+ font.mapUsedGlyphName(gid,
+ CFFStandardString.getName(sid));
+ } else {
+ int index = sid - 391;
+ if (index < cffReader.getStringIndex().getNumObjects()) {
+ font.mapUsedGlyphName(gid,
+ new String(cffReader.getStringIndex().getValue(index)));
+ } else {
+ font.mapUsedGlyphName(gid, ".notdef");
+ }
+ }
+ }
+ bytes = OTFFile.getCFFData(reader);
+ } else {
+ psName = font.getEmbedFontName();
+ OTFSubSetFile otfFile = new OTFSubSetFile();
+ otfFile.readFont(reader, psName, header, font);
+ bytes = otfFile.getFontSubset();
+ }
+
+ gen.writeln("%!PS-Adobe-3.0 Resource-FontSet");
+ gen.writeln("%%DocumentNeedResources:ProcSet(FontSetInit)");
+ gen.writeln("%%Title:(FontSet/" + psName + ")");
+ gen.writeln("%%Version: 1.000");
+ gen.writeln("%%EndComments");
+ gen.writeln("%%IncludeResource:ProcSet(FontSetInit)");
+ gen.writeln("%%BeginResource: FontSet (" + psName + ")");
+ gen.writeln("/FontSetInit /ProcSet findresource begin");
+ //Next line + 1
+ String fontDeclaration = "/" + psName + " " + bytes.length + " StartData";
+ gen.writeln("%%BeginData: " + (fontDeclaration.length() + 1 + bytes.length) + " Binary Bytes");
+ gen.writeln(fontDeclaration);
+ gen.writeByteArr(bytes);
+ gen.writeln("%%EndData");
+ gen.writeln("%%EndResource");
+
+ gen.writeln("/" + psName + ".0.enc [ ");
+ int lengthCount = 0;
+ int charCount = 1;
+ int encodingCount = 0;
+ String line = "";
+ for (int gid : font.getUsedGlyphNames().keySet()) {
+ line += "/" + font.getUsedGlyphNames().get(gid) + " ";
+ lengthCount++;
+ charCount++;
+ if (lengthCount == 8) {
+ gen.writeln(line);
+ line = "";
+ lengthCount = 0;
+ }
+ if (charCount > 256) {
+ encodingCount++;
+ charCount = 1;
+ gen.writeln(line);
+ line = "";
+ lengthCount = 0;
+ gen.writeln("] def");
+ gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName,
+ encodingCount - 1, psName, encodingCount - 1, psName));
+ gen.writeln("/" + psName + "." + encodingCount + ".enc [ ");
+ }
+ }
+ gen.writeln(line);
+ gen.writeln("] def");
+ gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount,
+ psName, encodingCount, psName));
+ }
+
private static PSResource embedType2CIDFont(PSGenerator gen,
MultiByteFont font, InputStream fontStream) throws IOException {
assert font.getCIDType() == CIDFontType.CIDTYPE2;
@@ -502,17 +610,18 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
}
gen.writeln(">] def");
FontFileReader reader = new FontFileReader(fontStream);
+ String header = OFFontLoader.readHeader(reader);
TTFFile ttfFile;
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
ttfFile = new TTFSubSetFile();
- ttfFile.readFont(reader, font.getTTCName(), font.getUsedGlyphs());
+ //Change the TTFFile to have the abstract method for TTFSubSetFile
+ ((TTFSubSetFile)ttfFile).readFont(reader, font.getTTCName(), header, font.getUsedGlyphs());
} else {
ttfFile = new TTFFile();
ttfFile.readFont(reader, font.getTTCName());
}
-
createType42DictionaryEntries(gen, font, new CMapSegment[0], ttfFile);
gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop");
gen.writeln("end");
@@ -670,7 +779,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
private static PSResource defineDerivedTrueTypeFont(PSGenerator gen,
PSEventProducer eventProducer, String baseFontName, String fontName,
SingleByteEncoding encoding, CMapSegment[] cmap) throws IOException {
- checkPostScriptLevel3(gen, eventProducer);
+ checkPostScriptLevel3(gen, eventProducer, "TrueType");
PSResource res = new PSResource(PSResource.TYPE_FONT, fontName);
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res);
gen.commentln("%XGCDependencies: font " + baseFontName);
diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java
index b0b370c79..3d1887f2d 100644
--- a/src/java/org/apache/fop/render/ps/PSPainter.java
+++ b/src/java/org/apache/fop/render/ps/PSPainter.java
@@ -40,6 +40,7 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;
+import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.LazyFont;
@@ -375,7 +376,13 @@ public class PSPainter extends AbstractIFPainter {
}
Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints);
- useFont(fontKey, sizeMillipoints);
+ PSFontResource res = getDocumentHandler().getPSResourceForFontKey(fontKey);
+ if (tf instanceof MultiByteFont && ((MultiByteFont)tf).isOTFFile()) {
+ generator.writeln("/" + res.getName() + ".0 "
+ + generator.formatDouble(sizeMillipoints / 1000f) + " F");
+ } else {
+ useFont(fontKey, sizeMillipoints);
+ }
if (dp != null && dp[0] != null) {
x += dp[0][0];
@@ -408,7 +415,30 @@ public class PSPainter extends AbstractIFPainter {
}
}
} else {
- useFont(fontKey, sizeMillipoints);
+ if (tf instanceof MultiByteFont && ((MultiByteFont)tf).isOTFFile()) {
+ //Analyze string and split up in order to paint in different sub-fonts/encodings
+ int curEncoding = 0;
+ for (int i = start; i < textLen; i++) {
+ char orgChar = text.charAt(i);
+
+ MultiByteFont mbFont = (MultiByteFont)tf;
+ int origGlyphIdx = mbFont.findGlyphIndex(orgChar);
+ int newGlyphIdx = mbFont.getUsedGlyphs().get(origGlyphIdx);
+ int encoding = newGlyphIdx / 256;
+ if (encoding != curEncoding) {
+ if (i != 0) {
+ writeText(text, start, i - start, letterSpacing, wordSpacing, dp, font, tf,
+ true);
+ start = i;
+ }
+ generator.writeln("/" + res.getName() + "." + encoding + " "
+ + generator.formatDouble(sizeMillipoints / 1000f) + " F");
+ curEncoding = encoding;
+ }
+ }
+ } else {
+ useFont(fontKey, sizeMillipoints);
+ }
}
writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, tf,
tf instanceof MultiByteFont);
@@ -431,12 +461,14 @@ public class PSPainter extends AbstractIFPainter {
int lineStart = 0;
StringBuffer accText = new StringBuffer(initialSize);
StringBuffer sb = new StringBuffer(initialSize);
+ boolean isOTF = multiByte && ((MultiByteFont)tf).isOTFFile();
for (int i = start; i < end; i++) {
char orgChar = text.charAt(i);
char ch;
int cw;
int xGlyphAdjust = 0;
int yGlyphAdjust = 0;
+
if (CharUtilities.isFixedWidthSpace(orgChar)) {
//Fixed width space are rendered as spaces so copy/paste works in a reader
ch = font.mapChar(CharUtilities.SPACE);
@@ -460,11 +492,16 @@ public class PSPainter extends AbstractIFPainter {
xGlyphAdjust -= dp[i + 1][0];
yGlyphAdjust += dp[i + 1][1];
}
- if (multiByte) {
- accText.append(HexEncoder.encode(ch));
- } else {
+ if (!multiByte || isOTF) {
char codepoint = (char)(ch % 256);
- PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text
+ if (isOTF) {
+ codepoint -= (((MultiByteFont)tf).getEmbeddingMode() == EmbeddingMode.FULL) ? 0 : 1;
+ accText.append(HexEncoder.encode(codepoint, 2));
+ } else {
+ PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text
+ }
+ } else {
+ accText.append(HexEncoder.encode(ch));
}
if (xGlyphAdjust != 0 || yGlyphAdjust != 0) {
needTJ = true;
diff --git a/test/java/org/apache/fop/fonts/cff/CFFDataReaderTestCase.java b/test/java/org/apache/fop/fonts/cff/CFFDataReaderTestCase.java
new file mode 100644
index 000000000..64c7642eb
--- /dev/null
+++ b/test/java/org/apache/fop/fonts/cff/CFFDataReaderTestCase.java
@@ -0,0 +1,154 @@
+/*
+ * 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.cff;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Random;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.fontbox.cff.CFFDataInput;
+
+import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData;
+import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
+import org.apache.fop.fonts.truetype.OTFSubSetFile;
+
+import static org.junit.Assert.assertEquals;
+
+public class CFFDataReaderTestCase {
+ private CFFDataReader cffReader;
+
+ /**
+ * Initializes the CFFDataReader for testing purposes
+ */
+ @Before
+ public void setUp() {
+ cffReader = new CFFDataReader();
+ }
+
+ /**
+ * Parses a test dictionary to verify whether the stored data is read correctly.
+ * @throws IOException
+ */
+ @Test
+ public void parseDictData() throws IOException {
+ byte[] testDictData = prepareDictData();
+ Map testTopDict = cffReader.parseDictData(testDictData);
+ validateDictData(testTopDict);
+ }
+
+ private byte[] prepareDictData() {
+ byte[] testDictData = new byte[0];
+ //Version
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 392, new int[] { 0 }, -1));
+ //Notice
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 393, new int[] { 1 }, -1));
+ //Copyright
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 394, new int[] { 12, 0 }, -1));
+ //FullName
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 395, new int[] { 2 }, -1));
+ //FamilyName
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 396, new int[] { 3 }, -1));
+ //Weight
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 397, new int[] { 4 }, -1));
+ //isFixedPitch (boolean = false)
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 0, new int[] { 12, 1 }, -1));
+ //FontBBox
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ -50, new int[0], -1));
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ -40, new int[0], -1));
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 100, new int[0], -1));
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 120, new int[] { 5 }, -1));
+ //charset
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 1234, new int[] { 15 }, -1));
+ //CharStrings
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 3654, new int[] { 17 }, -1));
+ //Private
+ testDictData = OTFSubSetFile.concatArray(testDictData, OTFSubSetFile.createNewRef(
+ 11454, new int[] { 18 }, -1));
+ return testDictData;
+ }
+
+ private void validateDictData(Map dictMap) {
+ //SID Values (numbers)
+ assertEquals(dictMap.get("version").getOperands().get(0).intValue(), 392);
+ assertEquals(dictMap.get("Notice").getOperands().get(0).intValue(), 393);
+ assertEquals(dictMap.get("Copyright").getOperands().get(0).intValue(), 394);
+ assertEquals(dictMap.get("FullName").getOperands().get(0).intValue(), 395);
+ assertEquals(dictMap.get("FamilyName").getOperands().get(0).intValue(), 396);
+ assertEquals(dictMap.get("Weight").getOperands().get(0).intValue(), 397);
+ //Boolean comparison
+ assertEquals(dictMap.get("isFixedPitch").getOperands().get(0).intValue(), 0);
+ //Array comparison
+ int[] fontBBox = { -50, -40, 100, 120 };
+ DICTEntry fontBBoxEntry = dictMap.get("FontBBox");
+ for (int i = 0;i < fontBBoxEntry.getOperands().size();i++) {
+ assertEquals(fontBBoxEntry.getOperands().get(i).intValue(), fontBBox[i]);
+ }
+ //Multi-byte offset (number)
+ assertEquals(dictMap.get("charset").getOperands().get(0).intValue(), 1234);
+ assertEquals(dictMap.get("CharStrings").getOperands().get(0).intValue(), 3654);
+ //Larger offset
+ assertEquals(dictMap.get("Private").getOperands().get(0).intValue(), 11454);
+ }
+
+ /**
+ * Tests the parsing of an example byte data index structure
+ * @throws IOException
+ */
+ @Test
+ public void testIndexParsing() throws IOException {
+ byte[] testIndex = {
+ 0, 5, //Number of objects
+ 1, //Offset size
+ 1, //Offsets...
+ 5,
+ 12,
+ 24,
+ 27,
+ 32
+ };
+ Random randGen = new Random();
+ byte[] data = new byte[31];
+ for (int i = 0;i < data.length;i++) {
+ data[i] = (byte)randGen.nextInt(255);
+ }
+ testIndex = OTFSubSetFile.concatArray(testIndex, data);
+ CFFIndexData indexData = cffReader.readIndex(new CFFDataInput(testIndex));
+ assertEquals(indexData.getNumObjects(), 5);
+ assertEquals(indexData.getOffSize(), 1);
+ assertEquals(indexData.getOffsets().length, 6);
+ assertEquals(indexData.getOffsets()[5], 32);
+ }
+}
diff --git a/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java b/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java
index 204803a32..e794ab1a8 100644
--- a/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java
+++ b/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java
@@ -148,14 +148,15 @@ public class GlyfTableTestCase {
private void setupSubsetReader(Map glyphs) throws IOException {
TTFSubSetFile fontFile = new TTFSubSetFile();
- fontFile.readFont(originalFontReader, "Deja", glyphs);
+ String header = OFFontLoader.readHeader(subsetReader);
+ fontFile.readFont(originalFontReader, "Deja", header, glyphs);
byte[] subsetFont = fontFile.getFontSubset();
InputStream intputStream = new ByteArrayInputStream(subsetFont);
subsetReader = new FontFileReader(intputStream);
}
private void readLoca() throws IOException {
- DirData loca = getTableData("loca");
+ DirData loca = getTableData(OFTableName.LOCA.getName());
int numberOfGlyphs = (int) (loca.length - 4) / 4;
glyphOffsets = new long[numberOfGlyphs];
subsetReader.seekSet(loca.offset);
@@ -166,7 +167,7 @@ public class GlyfTableTestCase {
}
private int[] retrieveIndicesOfComposedGlyphs() throws IOException {
- DirData glyf = getTableData("glyf");
+ DirData glyf = getTableData(OFTableName.GLYF.getName());
int[] composedGlyphIndices = new int[glyphOffsets.length];
for (int i = 0; i < glyphOffsets.length; i++) {
diff --git a/test/java/org/apache/fop/fonts/truetype/OTFFileTestCase.java b/test/java/org/apache/fop/fonts/truetype/OTFFileTestCase.java
new file mode 100644
index 000000000..737c2e05e
--- /dev/null
+++ b/test/java/org/apache/fop/fonts/truetype/OTFFileTestCase.java
@@ -0,0 +1,86 @@
+/*
+ * 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.truetype;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OTFFileTestCase {
+ protected OTFFile sourceSansProBold;
+ protected FontFileReader sourceSansReader = null;
+ protected OTFFile alexBrush;
+ protected FontFileReader alexBrushReader = null;
+
+ /**
+ * Initializes fonts used for the testing of reading OTF CFF
+ * @throws IOException
+ */
+ @Before
+ public void setUp() throws Exception {
+ sourceSansProBold = new OTFFile();
+ InputStream sourceSansStream = new FileInputStream("test/resources/fonts/otf/SourceSansProBold.otf");
+ sourceSansReader = new FontFileReader(sourceSansStream);
+ String sourceSansHeader = OFFontLoader.readHeader(sourceSansReader);
+ sourceSansProBold.readFont(sourceSansReader, sourceSansHeader);
+ sourceSansStream.close();
+
+ InputStream alexBrushStream = new FileInputStream("test/resources/fonts/otf/AlexBrushRegular.otf");
+ alexBrush = new OTFFile();
+ alexBrushReader = new FontFileReader(alexBrushStream);
+ String carolynaHeader = OFFontLoader.readHeader(alexBrushReader);
+ alexBrush.readFont(alexBrushReader, carolynaHeader);
+ alexBrushStream.close();
+ }
+
+ /**
+ * Tests the font names being read from the file
+ */
+ @Test
+ public void testFontNames() {
+ assertTrue(sourceSansProBold.getFamilyNames().contains("Source Sans Pro"));
+ assertTrue(alexBrush.getFamilyNames().contains("Alex Brush"));
+ }
+
+ /**
+ * Tests the number of glyphs and a select number of widths from each font
+ */
+ @Test
+ public void testGlyphNumberAndWidths() {
+ assertEquals(824, sourceSansProBold.numberOfGlyphs);
+ assertEquals(256, alexBrush.numberOfGlyphs);
+
+ int[] gids = {32, 42, 44, 47};
+ int[] sourceSansWidths = {516, 555, 572, 383};
+ for (int i = 0;i < gids.length;i++) {
+ assertEquals(sourceSansWidths[i], sourceSansProBold.getWidths()[gids[i]]);
+ }
+ int[] carolynaWidths = {842, 822, 658, 784};
+ for (int i = 0;i < gids.length;i++) {
+ assertEquals(carolynaWidths[i], alexBrush.getWidths()[gids[i]]);
+ }
+ }
+}
diff --git a/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java b/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java
new file mode 100644
index 000000000..604cca3a8
--- /dev/null
+++ b/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java
@@ -0,0 +1,148 @@
+/*
+ * 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.truetype;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.fontbox.cff.CFFDataInput;
+import org.apache.fontbox.cff.CFFFont;
+import org.apache.fontbox.cff.CFFParser;
+import org.apache.fontbox.cff.IndexData;
+import org.apache.fontbox.cff.Type2CharStringParser;
+
+import org.apache.fop.fonts.cff.CFFDataReader;
+import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData;
+import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OTFSubSetFileTestCase extends OTFFileTestCase {
+
+ CFFDataReader cffReaderSourceSans;
+ private OTFSubSetFile sourceSansSubset;
+ private byte[] sourceSansData;
+ CFFDataReader cffReaderHeitiStd;
+
+ public OTFSubSetFileTestCase() throws IOException {
+ super();
+ }
+
+ /**
+ * Initialises the test by creating the font subset. A CFFDataReader is
+ * also created based on the subset data for use in the tests.
+ * @throws IOException
+ */
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ Map glyphs = new HashMap();
+ for (int i = 0; i < 256; i++) {
+ glyphs.put(i, i);
+ }
+
+ sourceSansSubset = new OTFSubSetFile();
+ String sourceSansHeader = OFFontLoader.readHeader(sourceSansReader);
+ sourceSansSubset.readFont(sourceSansReader, "SourceSansProBold", sourceSansHeader, glyphs);
+ sourceSansData = sourceSansSubset.getFontSubset();
+ cffReaderSourceSans = new CFFDataReader(sourceSansData);
+ }
+
+ /**
+ * Validates the CharString data against the original font
+ * @throws IOException
+ */
+ @Test
+ public void testCharStringIndex() throws IOException {
+ assertEquals(256, cffReaderSourceSans.getCharStringIndex().getNumObjects());
+ assertTrue(checkCorrectOffsets(cffReaderSourceSans.getCharStringIndex()));
+ validateCharStrings(cffReaderSourceSans);
+ }
+
+ private boolean checkCorrectOffsets(CFFIndexData indexData) {
+ int last = 0;
+ for (int i = 0;i < indexData.getOffsets().length;i++) {
+ if (indexData.getOffsets()[i] < last) {
+ return false;
+ }
+ last = indexData.getOffsets()[i];
+ }
+ return true;
+ }
+
+ private void validateCharStrings(CFFDataReader cffReader) throws IOException {
+ CFFFont sourceSansOriginal = sourceSansProBold.fileFont;
+ Map origCharStringData = sourceSansOriginal.getCharStringsDict();
+ IndexData origGlobalIndex = sourceSansOriginal.getGlobalSubrIndex();
+ IndexData origLocalIndex = sourceSansOriginal.getLocalSubrIndex();
+
+ CFFDataInput globalSubrs = new CFFDataInput(cffReader.getGlobalIndexSubr().getByteData());
+ CFFDataInput localSubrs = new CFFDataInput(cffReader.getLocalIndexSubr().getByteData());
+
+ IndexData globalIndexData = CFFParser.readIndexData(globalSubrs);
+ IndexData localIndexData = CFFParser.readIndexData(localSubrs);
+
+ CFFIndexData charStrings = cffReader.getCharStringIndex();
+ for (int i = 0;i < charStrings.getNumObjects();i++) {
+ byte[] charData = charStrings.getValue(i);
+ Type2CharStringParser parser = new Type2CharStringParser();
+
+ byte[] origCharData = origCharStringData.get(origCharStringData.keySet().toArray(
+ new String[0])[i]);
+ List