From 4104b0bbe7556a0681b1522e94665f79b6d7f65c Mon Sep 17 00:00:00 2001 From: Glenn Adams Date: Tue, 28 Feb 2012 09:38:21 +0000 Subject: [PATCH] integrate (very) preliminary cff support git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_CFF@1294537 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fop/fonts/truetype/CFFUtil.java | 169 ++++++++++++++++++ .../fop/fonts/truetype/TTFFontLoader.java | 5 - .../fop/fonts/truetype/TTFSubSetFile.java | 120 +++++++++---- 3 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 src/java/org/apache/fop/fonts/truetype/CFFUtil.java diff --git a/src/java/org/apache/fop/fonts/truetype/CFFUtil.java b/src/java/org/apache/fop/fonts/truetype/CFFUtil.java new file mode 100644 index 000000000..6b2d704a1 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/CFFUtil.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; + +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 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[] {}; + } + +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java index 54324be52..9612dee00 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java @@ -109,11 +109,6 @@ public class TTFFontLoader extends FontLoader { 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; diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java index e081734be..5d12fd1e9 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java @@ -43,6 +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 int cffDirOffset = 0; private int cvtDirOffset = 0; private int fpgmDirOffset = 0; private int glyfDirOffset = 0; @@ -86,10 +87,8 @@ public class TTFSubSetFile extends TTFFile { 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++; } @@ -99,6 +98,7 @@ public class TTFSubSetFile extends TTFFile { if (hasPrep()) { numTables++; } + numTables += 2; //1 req'd table: loca,glyf } return numTables; } @@ -109,10 +109,17 @@ public class TTFSubSetFile extends TTFFile { 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); @@ -131,24 +138,6 @@ public class TTFSubSetFile extends TTFFile { 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; @@ -165,22 +154,49 @@ public class TTFSubSetFile extends TTFFile { 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; + } + } @@ -441,6 +457,35 @@ public class TTFSubSetFile extends TTFFile { } } + /** + * 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 @@ -509,9 +554,10 @@ public class TTFSubSetFile extends TTFFile { 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 @@ -539,8 +585,12 @@ public class TTFSubSetFile extends TTFFile { 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(); -- 2.39.5