--- /dev/null
+/*
+ * 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.Map;
+
+/**
+ * A utility class for working with Adobe Compact Font Format (CFF) data. See
+ * Adobe Technical Note #5176 "The Compact Font Format Specification" for more
+ * information.
+ */
+final class CFFUtil {
+
+ private CFFUtil() {
+ }
+
+ /**
+ * Extract glyph subset of CFF table ENTRY from input font IN according to
+ * the specified GLYPHS.
+ * @param in input font file reader
+ * @param entry directory entry describing CFF table in input file
+ * @param glyphs map of original glyph indices to subset indices
+ * @returns an array of bytes representing a well formed CFF table containing
+ * the specified glyph subset
+ * @throws IOException in case of an I/O exception when reading from input font
+ */
+ public static byte[] extractGlyphSubset
+ ( FontFileReader in, TTFDirTabEntry entry, Map<Integer, Integer> glyphs )
+ throws IOException {
+
+ // 1. read CFF data from IN, where ENTRY points at start of CFF table
+ // 2. while reading CFF data, accumulate necessary information to output subset
+ // of glyphs, where GLYPHS.keySet() enumerates the desired glyphs, and
+ // GLYPHS.values() (for the keys) enumerates the new (output) glyph indices
+ // for the desired glyph subset
+ // 3. return a BLOB containing a well-formed CFF font according to the Adobe
+ // spec and constrained as needed by http://www.microsoft.com/typography/otspec/cff.htm
+
+ long cffOffset = entry.getOffset();
+
+ // HEADER
+
+ in.seekSet ( cffOffset );
+ int major = in.readTTFUByte();
+ int minor = in.readTTFUByte();
+ int hdrSize = in.readTTFUByte();
+ int hdrOffSize = in.readTTFUByte();
+
+ // Name INDEX
+
+ in.seekSet ( cffOffset + hdrSize );
+ int nameIndexCount = in.readTTFUShort();
+ if ( nameIndexCount > 0 ) {
+ int nameIndexOffsetSize = in.readTTFUByte();
+ long nameIndexOffsets[] = new long [ nameIndexCount + 1 ];
+ if ( nameIndexOffsetSize == 1 ) {
+ for ( int i = 0, n = nameIndexCount + 1; i < n; i++ ) {
+ nameIndexOffsets [ i ] = in.readTTFUByte();
+ }
+ } else if ( nameIndexOffsetSize == 2 ) {
+ for ( int i = 0, n = nameIndexCount + 1; i < n; i++ ) {
+ nameIndexOffsets [ i ] = in.readTTFUShort();
+ }
+ } else if ( nameIndexOffsetSize == 4 ) {
+ for ( int i = 0, n = nameIndexCount + 1; i < n; i++ ) {
+ nameIndexOffsets [ i ] = in.readTTFULong();
+ }
+ } else {
+ throw new RuntimeException ( "invalid offset size, got " + nameIndexOffsetSize + ", expected 1, 2, or 4" );
+ }
+ int nameIndexDataOffset = in.getCurrentPos() - 1;
+ String[] names = new String [ nameIndexCount ];
+ for ( int i = 0, n = names.length, nOffsets = nameIndexOffsets.length; i < n; i++ ) {
+ assert ( i + 1 ) < nOffsets;
+ long offCurrent = nameIndexOffsets [ i ];
+ long offNext = nameIndexOffsets [ i + 1 ];
+ long numBytes = offNext - offCurrent;
+ String name;
+ if ( numBytes > 0 ) {
+ if ( numBytes < Integer.MAX_VALUE ) {
+ long nameOffset = nameIndexDataOffset + offCurrent;
+ if ( nameOffset < Integer.MAX_VALUE ) {
+ byte[] nameBytes = in.getBytes ( (int) nameOffset, (int) numBytes );
+ name = new String ( nameBytes, 0, (int) numBytes, "US-ASCII" );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported index offset value, got " + nameOffset + ", expected less than " + Integer.MAX_VALUE );
+ }
+ } else {
+ throw new UnsupportedOperationException ( "unsupported indexed data length, got " + numBytes + ", expected less than " + Integer.MAX_VALUE );
+ }
+ } else {
+ name = "";
+ }
+ names [ i ] = name;
+ }
+ in.seekSet ( nameIndexDataOffset + nameIndexOffsets [ nameIndexCount ] );
+ }
+
+ // Top Dict INDEX
+
+ int topDictIndexCount = in.readTTFUShort();
+ if ( topDictIndexCount > 0 ) {
+ int topDictIndexOffsetSize = in.readTTFUByte();
+ long topDictIndexOffsets[] = new long [ topDictIndexCount + 1 ];
+ if ( topDictIndexOffsetSize == 1 ) {
+ for ( int i = 0, n = topDictIndexCount + 1; i < n; i++ ) {
+ topDictIndexOffsets [ i ] = in.readTTFUByte();
+ }
+ } else if ( topDictIndexOffsetSize == 2 ) {
+ for ( int i = 0, n = topDictIndexCount + 1; i < n; i++ ) {
+ topDictIndexOffsets [ i ] = in.readTTFUShort();
+ }
+ } else if ( topDictIndexOffsetSize == 4 ) {
+ for ( int i = 0, n = topDictIndexCount + 1; i < n; i++ ) {
+ topDictIndexOffsets [ i ] = in.readTTFULong();
+ }
+ } else {
+ throw new RuntimeException ( "invalid offset size, got " + topDictIndexOffsetSize + ", expected 1, 2, or 4" );
+ }
+ int topDictIndexDataOffset = in.getCurrentPos() - 1;
+ byte[][] topDicts = new byte [ topDictIndexCount ][];
+ for ( int i = 0, n = topDicts.length, nOffsets = topDictIndexOffsets.length; i < n; i++ ) {
+ assert ( i + 1 ) < nOffsets;
+ long offCurrent = topDictIndexOffsets [ i ];
+ long offNext = topDictIndexOffsets [ i + 1 ];
+ long numBytes = offNext - offCurrent;
+ byte[] topDict;
+ if ( numBytes > 0 ) {
+ if ( numBytes < Integer.MAX_VALUE ) {
+ long topDictOffset = topDictIndexDataOffset + offCurrent;
+ if ( topDictOffset < Integer.MAX_VALUE ) {
+ byte[] topDictBytes = in.getBytes ( (int) topDictOffset, (int) numBytes );
+ topDict = topDictBytes;
+ } else {
+ throw new UnsupportedOperationException ( "unsupported index offset value, got " + topDictOffset + ", expected less than " + Integer.MAX_VALUE );
+ }
+ } else {
+ throw new UnsupportedOperationException ( "unsupported indexed data length, got " + numBytes + ", expected less than " + Integer.MAX_VALUE );
+ }
+ } else {
+ topDict = new byte [ 0 ];
+ }
+ topDicts [ i ] = topDict;
+ }
+ in.seekSet ( topDictIndexDataOffset + topDictIndexOffsets [ topDictIndexCount ] );
+ }
+
+ return new byte[] {};
+ }
+
+}
* Offsets in name table to be filled out by table.
* The offsets are to the checkSum field
*/
+ private int cffDirOffset = 0;
private int cvtDirOffset = 0;
private int fpgmDirOffset = 0;
private int glyfDirOffset = 0;
private int determineTableCount() {
int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp
if (isCFF()) {
- throw new UnsupportedOperationException(
- "OpenType fonts with CFF glyphs are not supported");
+ numTables += 1; //1 req'd table: CFF
} else {
- numTables += 2; //1 req'd table: glyf,loca
if (hasCvt()) {
numTables++;
}
if (hasPrep()) {
numTables++;
}
+ numTables += 2; //1 req'd table: loca,glyf
}
return numTables;
}
private void createDirectory() {
int numTables = determineTableCount();
// Create the TrueType header
- writeByte((byte)0);
- writeByte((byte)1);
- writeByte((byte)0);
- writeByte((byte)0);
+ if (isCFF() ) {
+ writeByte((byte)'O');
+ writeByte((byte)'T');
+ writeByte((byte)'T');
+ writeByte((byte)'O');
+ } else {
+ writeByte((byte)0);
+ writeByte((byte)1);
+ writeByte((byte)0);
+ writeByte((byte)0);
+ }
realSize += 4;
writeUShort(numTables);
realSize += 2;
// Create space for the table entries
- if (hasCvt()) {
- writeString("cvt ");
- cvtDirOffset = currentPos;
- currentPos += 12;
- realSize += 16;
- }
-
- if (hasFpgm()) {
- writeString("fpgm");
- fpgmDirOffset = currentPos;
- currentPos += 12;
- realSize += 16;
- }
-
- writeString("glyf");
- glyfDirOffset = currentPos;
- currentPos += 12;
- realSize += 16;
writeString("head");
headDirOffset = currentPos;
currentPos += 12;
realSize += 16;
- writeString("loca");
- locaDirOffset = currentPos;
- currentPos += 12;
- realSize += 16;
-
writeString("maxp");
maxpDirOffset = currentPos;
currentPos += 12;
realSize += 16;
+ if (hasCvt()) {
+ writeString("cvt ");
+ cvtDirOffset = currentPos;
+ currentPos += 12;
+ realSize += 16;
+ }
+
+ if (hasFpgm()) {
+ writeString("fpgm");
+ fpgmDirOffset = currentPos;
+ currentPos += 12;
+ realSize += 16;
+ }
+
if (hasPrep()) {
writeString("prep");
prepDirOffset = currentPos;
currentPos += 12;
realSize += 16;
}
+
+ if (isCFF()) {
+ writeString("CFF ");
+ cffDirOffset = currentPos;
+ currentPos += 12;
+ realSize += 16;
+ } else {
+ writeString("loca");
+ locaDirOffset = currentPos;
+ currentPos += 12;
+ realSize += 16;
+
+ writeString("glyf");
+ glyfDirOffset = currentPos;
+ currentPos += 12;
+ realSize += 16;
+ }
+
}
}
}
+ /**
+ * Create the CFF table
+ */
+ private void createCff(FontFileReader in, Map glyphs) throws IOException {
+ TTFDirTabEntry entry = (TTFDirTabEntry) dirTabs.get("CFF ");
+ int size = 0;
+ int start = 0;
+ if ( entry != null ) {
+ pad4();
+ start = currentPos;
+
+ byte[] cffData = CFFUtil.extractGlyphSubset ( in, entry, glyphs );
+ int cffDataLength = cffData.length;
+ System.arraycopy ( cffData, 0, output, currentPos, cffDataLength );
+ currentPos += cffDataLength;
+ realSize += cffDataLength;
+ size = currentPos - start;
+
+ int checksum = getCheckSum(start, size);
+ writeULong(cffDirOffset, checksum);
+ writeULong(cffDirOffset + 4, start);
+ writeULong(cffDirOffset + 8, size);
+ currentPos += 12;
+ realSize += 12;
+
+ } else {
+ throw new IOException("Can't find CFF table");
+ }
+ }
/**
* Create the hmtx table by copying metrics from original
getNumGlyphs(in);
readHorizontalHeader(in);
readHorizontalMetrics(in);
- readIndexToLocation(in);
-
- scanGlyphs(in, subsetGlyphs);
+ if (!isCFF()) {
+ readIndexToLocation(in);
+ scanGlyphs(in, subsetGlyphs);
+ }
createDirectory(); // Create the TrueType header and directory
log.debug("TrueType: prep table not present. Skipped.");
}
- createLoca(subsetGlyphs.size()); // create empty loca table
- createGlyf(in, subsetGlyphs); //create glyf table and update loca table
+ if (isCFF()) {
+ createCff(in, subsetGlyphs);
+ } else {
+ createLoca(subsetGlyphs.size()); // create empty loca table
+ createGlyf(in, subsetGlyphs); //create glyf table and update loca table
+ }
pad4();
createCheckSumAdjustment();