blob: b98919ca232fde1bb5da59facbc6df9143c4a3f5 [file] [log] [blame]
/*
* 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.render.ps;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
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;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;
import org.apache.xmlgraphics.ps.dsc.ResourceTracker;
import org.apache.fop.fonts.Base14Font;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CIDSet;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.LazyFont;
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.TTFOutputStream;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.type1.Type1SubsetFile;
import org.apache.fop.render.ps.fonts.PSTTFOutputStream;
import org.apache.fop.util.HexEncoder;
/**
* Utility code for font handling in PostScript.
*/
public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
/** logging instance */
protected static final Log log = LogFactory.getLog(PSFontUtils.class);
/**
* Generates the PostScript code for the font dictionary. This method should only be
* used if no "resource optimization" is performed, i.e. when the fonts are not embedded
* in a second pass.
* @param gen PostScript generator to use for output
* @param fontInfo available fonts
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @throws IOException in case of an I/O problem
*/
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo)
throws IOException {
return writeFontDict(gen, fontInfo, null);
}
/**
* Generates the PostScript code for the font dictionary. This method should only be
* used if no "resource optimization" is performed, i.e. when the fonts are not embedded
* in a second pass.
* @param gen PostScript generator to use for output
* @param fontInfo available fonts
* @param eventProducer to report events
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @throws IOException in case of an I/O problem
*/
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo,
PSEventProducer eventProducer) throws IOException {
return writeFontDict(gen, fontInfo, fontInfo.getFonts(), true, eventProducer);
}
/**
* Generates the PostScript code for the font dictionary. This method assumes all used
* fonts and characters are known, i.e. when PostScript is generated with resource
* optimization turned on.
* @param gen PostScript generator to use for output
* @param fontInfo available fonts
* @param fonts the set of fonts to work with
* @param eventProducer the event producer
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @throws IOException in case of an I/O problem
*/
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, Map<String, Typeface> fonts,
PSEventProducer eventProducer) throws IOException {
return writeFontDict(gen, fontInfo, fonts, false, eventProducer);
}
/**
* Generates the PostScript code for the font dictionary.
* @param gen PostScript generator to use for output
* @param fontInfo available fonts
* @param fonts the set of fonts to work with
* @param encodeAllCharacters true if all characters shall be encoded using additional,
* generated encodings.
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @throws IOException in case of an I/O problem
*/
private static Map writeFontDict(PSGenerator gen, FontInfo fontInfo,
Map<String, Typeface> fonts, boolean encodeAllCharacters, PSEventProducer eventProducer)
throws IOException {
gen.commentln("%FOPBeginFontDict");
Map fontResources = new HashMap();
for (String key : fonts.keySet()) {
Typeface tf = getTypeFace(fontInfo, fonts, key);
PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName());
PSFontResource fontResource = embedFont(gen, tf, fontRes, eventProducer);
fontResources.put(key, fontResource);
if (tf instanceof SingleByteFont) {
SingleByteFont sbf = (SingleByteFont)tf;
if (encodeAllCharacters) {
sbf.encodeAllUnencodedCharacters();
}
for (int i = 0, c = sbf.getAdditionalEncodingCount(); i < c; i++) {
SingleByteEncoding encoding = sbf.getAdditionalEncoding(i);
defineEncoding(gen, encoding);
String postFix = "_" + (i + 1);
PSResource derivedFontRes;
if (tf.getFontType() == FontType.TRUETYPE
&& sbf.getTrueTypePostScriptVersion() != PostScriptVersion.V2) {
derivedFontRes = defineDerivedTrueTypeFont(gen, eventProducer,
tf.getEmbedFontName(), tf.getEmbedFontName() + postFix, encoding,
sbf.getCMap());
} else {
derivedFontRes = defineDerivedFont(gen, tf.getEmbedFontName(),
tf.getEmbedFontName() + postFix, encoding.getName());
}
fontResources.put(key + postFix,
PSFontResource.createFontResource(derivedFontRes));
}
}
}
gen.commentln("%FOPEndFontDict");
reencodeFonts(gen, fonts);
return fontResources;
}
private static void reencodeFonts(PSGenerator gen, Map<String, Typeface> fonts)
throws IOException {
ResourceTracker tracker = gen.getResourceTracker();
if (!tracker.isResourceSupplied(WINANSI_ENCODING_RESOURCE)) {
//Only out Base 14 fonts still use that
for (Typeface tf : fonts.values()) {
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
if (tf instanceof SingleByteFont
&& ((SingleByteFont) tf).getEncoding().getName().equals("custom")) {
defineEncoding(gen, ((SingleByteFont) tf).getEncoding());
}
}
}
defineWinAnsiEncoding(gen);
}
gen.commentln("%FOPBeginFontReencode");
//Rewrite font encodings
for (String key : fonts.keySet()) {
Typeface tf = fonts.get(key);
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
if (tf == null) {
continue;
}
}
if (null == tf.getEncodingName()) {
//ignore (ZapfDingbats and Symbol used to run through here, kept for safety reasons)
} else if ("SymbolEncoding".equals(tf.getEncodingName())) {
//ignore (no encoding redefinition)
} else if ("ZapfDingbatsEncoding".equals(tf.getEncodingName())) {
//ignore (no encoding redefinition)
} else {
if (tf instanceof Base14Font) {
//Our Base 14 fonts don't use the default encoding
redefineFontEncoding(gen, tf.getEmbedFontName(), tf.getEncodingName());
} else if (tf instanceof SingleByteFont) {
SingleByteFont sbf = (SingleByteFont)tf;
if (!sbf.isUsingNativeEncoding()) {
//Font has been configured to use an encoding other than the default one
redefineFontEncoding(gen, tf.getEmbedFontName(), tf.getEncodingName());
}
}
}
}
gen.commentln("%FOPEndFontReencode");
}
private static Typeface getTypeFace(FontInfo fontInfo, Map<String, Typeface> fonts,
String key) {
Typeface tf = fonts.get(key);
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
}
if (tf == null) {
//This is to avoid an NPE if a malconfigured font is in the configuration but not
//used in the document. If it were used, we wouldn't get this far.
String fallbackKey = fontInfo.getInternalFontKey(Font.DEFAULT_FONT);
tf = fonts.get(fallbackKey);
}
return tf;
}
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
|| fontType == FontType.TYPE0) || !(tf instanceof CustomFont)) {
gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
fontResource = PSFontResource.createFontResource(fontRes);
return fontResource;
}
CustomFont cf = (CustomFont)tf;
if (isEmbeddable(cf)) {
InputStream in = getInputStreamOnFont(gen, cf);
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, (SingleByteFont) tf, 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!");
}
}
if (!embeddedFont) {
gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
fontResource = PSFontResource.createFontResource(fontRes);
return fontResource;
}
return fontResource;
}
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 " + fontType + " fonts,"
+ " configured level is "
+ gen.getPSLevel());
}
}
}
private static void embedType1Font(PSGenerator gen, SingleByteFont font,
InputStream fontStream) throws IOException {
if (font.getEmbeddingMode() == EmbeddingMode.AUTO) {
font.setEmbeddingMode(EmbeddingMode.FULL);
}
byte[] fullFont = IOUtils.toByteArray(fontStream);
fontStream = new ByteArrayInputStream(fullFont);
boolean embed = true;
if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) {
Type1SubsetFile subset = new Type1SubsetFile();
byte[] byteSubset = subset.createSubset(fontStream, font);
fontStream = new ByteArrayInputStream(byteSubset);
}
embedType1Font(gen, fontStream);
if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) {
writeEncoding(gen, font);
}
}
private static void writeEncoding(PSGenerator gen, SingleByteFont font) throws IOException {
String psName = font.getEmbedFontName();
gen.writeln("/" + psName + ".0.enc [ ");
int lengthCount = 0;
int charCount = 1;
int encodingCount = 0;
StringBuilder line = new StringBuilder();
int lastGid = 0;
Set<Integer> keySet = font.getUsedGlyphNames().keySet();
for (int gid : keySet) {
for (int i = lastGid; i < gid - 1; i++) {
line.append("/.notdef ");
lengthCount++;
if (lengthCount == 8) {
gen.writeln(line.toString());
line = new StringBuilder();
lengthCount = 0;
}
}
lastGid = gid;
line.append(font.getUsedGlyphNames().get(gid) + " ");
lengthCount++;
charCount++;
if (lengthCount == 8) {
gen.writeln(line.toString());
line = new StringBuilder();
lengthCount = 0;
}
if (charCount > 256) {
encodingCount++;
charCount = 1;
gen.writeln(line.toString());
line = new StringBuilder();
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.toString());
gen.writeln("] def");
gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount,
psName, encodingCount, psName));
}
private static void embedTrueTypeFont(PSGenerator gen,
SingleByteFont font, InputStream fontStream) throws IOException {
/* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */
gen.commentln("%!PS-TrueTypeFont-65536-65536-1"); // TODO TrueType & font versions
gen.writeln("11 dict begin");
if (font.getEmbeddingMode() == EmbeddingMode.AUTO) {
font.setEmbeddingMode(EmbeddingMode.SUBSET);
}
FontFileReader reader = new FontFileReader(fontStream);
TTFFile ttfFile = new TTFFile();
ttfFile.readFont(reader, font.getFullName());
createType42DictionaryEntries(gen, font, font.getCMap(), ttfFile);
gen.writeln("FontName currentdict end definefont pop");
}
private static void createType42DictionaryEntries(PSGenerator gen, CustomFont font,
CMapSegment[] cmap, TTFFile ttfFile) throws IOException {
gen.write("/FontName /");
gen.write(font.getEmbedFontName());
gen.writeln(" def");
gen.writeln("/PaintType 0 def");
gen.writeln("/FontMatrix [1 0 0 1 0 0] def");
writeFontBBox(gen, font);
gen.writeln("/FontType 42 def");
gen.writeln("/Encoding 256 array");
gen.writeln("0 1 255{1 index exch/.notdef put}for");
boolean buildCharStrings;
Set<String> glyphNames = new HashSet<String>();
if (font.getFontType() == FontType.TYPE0 && font.getEmbeddingMode() != EmbeddingMode.FULL) {
//"/Encoding" is required but ignored for CID fonts
//so we keep it minimal to save space
buildCharStrings = false;
} else {
buildCharStrings = true;
for (int i = 0; i < Glyphs.WINANSI_ENCODING.length; i++) {
gen.write("dup ");
gen.write(i);
gen.write(" /");
String glyphName = Glyphs.charToGlyphName(Glyphs.WINANSI_ENCODING[i]);
if (glyphName.equals("")) {
gen.write(Glyphs.NOTDEF);
} else {
gen.write(glyphName);
glyphNames.add(glyphName);
}
gen.writeln(" put");
}
}
gen.writeln("readonly def");
TTFOutputStream ttfOut = new PSTTFOutputStream(gen);
ttfFile.stream(ttfOut);
buildCharStrings(gen, buildCharStrings, cmap, glyphNames, font);
}
private static void buildCharStrings(PSGenerator gen, boolean buildCharStrings,
CMapSegment[] cmap, Set<String> glyphNames, CustomFont font) throws IOException {
gen.write("/CharStrings ");
if (!buildCharStrings) {
gen.write(1);
} else if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
int charCount = 1; //1 for .notdef
for (CMapSegment segment : cmap) {
charCount += segment.getUnicodeEnd() - segment.getUnicodeStart() + 1;
}
gen.write(charCount);
} else {
gen.write(font.getCMap().length);
}
gen.writeln(" dict dup begin");
gen.write("/");
gen.write(Glyphs.NOTDEF);
gen.writeln(" 0 def"); // .notdef always has to be at index 0
if (!buildCharStrings) {
// If we're not building the full CharStrings we can end here
gen.writeln("end readonly def");
return;
}
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
//Only performed in singly-byte mode, ignored for CID fonts
for (CMapSegment segment : cmap) {
int glyphIndex = segment.getGlyphStartIndex();
for (int ch = segment.getUnicodeStart(); ch <= segment.getUnicodeEnd(); ch++) {
char ch16 = (char)ch; //TODO Handle Unicode characters beyond 16bit
String glyphName = Glyphs.charToGlyphName(ch16);
if ("".equals(glyphName)) {
glyphName = "u" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
}
writeGlyphDefs(gen, glyphName, glyphIndex);
glyphIndex++;
}
}
} else {
for (String name : glyphNames) {
writeGlyphDefs(gen, name,
getGlyphIndex(Glyphs.getUnicodeSequenceForGlyphName(name).charAt(0),
font.getCMap()));
}
}
gen.writeln("end readonly def");
}
private static void writeGlyphDefs(PSGenerator gen, String glyphName, int glyphIndex)
throws IOException {
gen.write("/");
gen.write(glyphName);
gen.write(" ");
gen.write(glyphIndex);
gen.writeln(" def");
}
private static int getGlyphIndex(char c, CMapSegment[] cmap) {
for (CMapSegment segment : cmap) {
if (segment.getUnicodeStart() <= c && c <= segment.getUnicodeEnd()) {
return segment.getGlyphStartIndex() + c - segment.getUnicodeStart();
}
}
return 0;
}
private static void composeType0Font(PSGenerator gen, MultiByteFont font,
InputStream fontStream) throws IOException {
String psName = font.getEmbedFontName();
gen.write("/");
gen.write(psName);
gen.write(" /Identity-H [/");
gen.write(psName);
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<String, DICTEntry> 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;
String psName = font.getEmbedFontName();
gen.write("%%BeginResource: CIDFont ");
gen.writeln(psName);
gen.write("%%Title: (");
gen.write(psName);
gen.writeln(" Adobe Identity 0)");
gen.writeln("%%Version: 1"); // TODO use font revision?
gen.writeln("/CIDInit /ProcSet findresource begin");
gen.writeln("20 dict begin");
gen.write("/CIDFontName /");
gen.write(psName);
gen.writeln(" def");
gen.writeln("/CIDFontVersion 1 def"); // TODO same as %%Version above
gen.write("/CIDFontType ");
gen.write(font.getCIDType().getValue());
gen.writeln(" def");
gen.writeln("/CIDSystemInfo 3 dict dup begin");
gen.writeln(" /Registry (Adobe) def");
gen.writeln(" /Ordering (Identity) def");
gen.writeln(" /Supplement 0 def");
gen.writeln("end def");
// TODO UIDBase (and UIDOffset in CMap) necessary if PostScript Level 1 & 2
// interpreters are to be supported
// (Level 1: with composite font extensions; Level 2: those that do not offer
// native mode support for CID-keyed fonts)
// TODO XUID (optional but strongly recommended)
// TODO /FontInfo
gen.write("/CIDCount ");
CIDSet cidSet = font.getCIDSet();
int numberOfGlyphs = cidSet.getNumberOfGlyphs();
gen.write(numberOfGlyphs);
gen.writeln(" def");
gen.writeln("/GDBytes 2 def"); // TODO always 2?
gen.writeln("/CIDMap [<");
int colCount = 0;
int lineCount = 1;
int nextBitSet = 0;
int previousBitSet = 0;
for (int cid = 0; cid < numberOfGlyphs; cid++) {
if (colCount++ == 20) {
gen.newLine();
colCount = 1;
if (lineCount++ == 800) {
gen.writeln("> <");
lineCount = 1;
}
}
String gid;
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
gid = HexEncoder.encode(cid, 4);
} else {
previousBitSet = nextBitSet;
nextBitSet = cidSet.getGlyphIndices().nextSetBit(nextBitSet);
while (previousBitSet++ < nextBitSet) {
// if there are gaps in the indices we pad them with zeros
gen.write("0000");
cid++;
if (colCount++ == 20) {
gen.newLine();
colCount = 1;
if (lineCount++ == 800) {
gen.writeln("> <");
lineCount = 1;
}
}
}
gid = HexEncoder.encode(nextBitSet, 4);
nextBitSet++;
}
gen.write(gid);
}
gen.writeln(">] def");
FontFileReader reader = new FontFileReader(fontStream);
String header = OFFontLoader.readHeader(reader);
TTFFile ttfFile;
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
ttfFile = new TTFSubSetFile();
//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");
gen.writeln("%%EndResource");
PSResource cidFontResource = new PSResource(PSResource.TYPE_CIDFONT, psName);
gen.getResourceTracker().registerSuppliedResource(cidFontResource);
return cidFontResource;
}
private static void writeFontBBox(PSGenerator gen, CustomFont font) throws IOException {
int[] bbox = font.getFontBBox();
gen.write("/FontBBox[");
for (int i = 0; i < 4; i++) {
gen.write(" ");
gen.write(bbox[i]);
}
gen.writeln(" ] def");
}
private static boolean isEmbeddable(CustomFont font) {
return font.isEmbeddable();
}
private static InputStream getInputStreamOnFont(PSGenerator gen, CustomFont font)
throws IOException {
if (isEmbeddable(font)) {
InputStream in = font.getInputStream();
if (in == null) {
return null;
}
//Make sure the InputStream is decorated with a BufferedInputStream
if (!(in instanceof java.io.BufferedInputStream)) {
in = new java.io.BufferedInputStream(in);
}
return in;
} else {
return null;
}
}
/**
* Determines the set of fonts that will be supplied with the PS file and registers them
* with the resource tracker. All the fonts that are being processed are returned as a Map.
* @param resTracker the resource tracker
* @param fontInfo available fonts
* @param fonts the set of fonts to work with
* @return a Map of PSResource instances representing all defined fonts (key: font key)
*/
public static Map determineSuppliedFonts(ResourceTracker resTracker,
FontInfo fontInfo, Map<String, Typeface> fonts) {
Map fontResources = new java.util.HashMap();
for (String key : fonts.keySet()) {
Typeface tf = getTypeFace(fontInfo, fonts, key);
PSResource fontRes = new PSResource("font", tf.getEmbedFontName());
fontResources.put(key, fontRes);
FontType fontType = tf.getFontType();
if (fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE
|| fontType == FontType.TYPE0) {
if (tf instanceof CustomFont) {
CustomFont cf = (CustomFont)tf;
if (isEmbeddable(cf)) {
if (fontType == FontType.TYPE0) {
resTracker.registerSuppliedResource(
new PSResource(PSResource.TYPE_CIDFONT, tf.getEmbedFontName()));
resTracker.registerSuppliedResource(
new PSResource(PSResource.TYPE_CMAP, "Identity-H"));
}
resTracker.registerSuppliedResource(fontRes);
}
if (tf instanceof SingleByteFont) {
SingleByteFont sbf = (SingleByteFont)tf;
for (int i = 0, c = sbf.getAdditionalEncodingCount(); i < c; i++) {
SingleByteEncoding encoding = sbf.getAdditionalEncoding(i);
PSResource encodingRes = new PSResource(
PSResource.TYPE_ENCODING, encoding.getName());
resTracker.registerSuppliedResource(encodingRes);
PSResource derivedFontRes = new PSResource(
PSResource.TYPE_FONT, tf.getEmbedFontName() + "_" + (i + 1));
resTracker.registerSuppliedResource(derivedFontRes);
}
}
}
}
}
return fontResources;
}
/**
* Defines the single-byte encoding for use in PostScript files.
* @param gen the PostScript generator
* @param encoding the single-byte encoding
* @return the PSResource instance that represents the encoding
* @throws IOException In case of an I/O problem
*/
public static PSResource defineEncoding(PSGenerator gen, SingleByteEncoding encoding)
throws IOException {
PSResource res = new PSResource(PSResource.TYPE_ENCODING, encoding.getName());
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res);
gen.writeln("/" + encoding.getName() + " [");
String[] charNames = encoding.getCharNameMap();
for (int i = 0; i < 256; i++) {
if (i > 0) {
if ((i % 5) == 0) {
gen.newLine();
} else {
gen.write(" ");
}
}
String glyphname = null;
if (i < charNames.length) {
glyphname = charNames[i];
}
if (glyphname == null || "".equals(glyphname)) {
glyphname = Glyphs.NOTDEF;
}
gen.write("/");
gen.write(glyphname);
}
gen.newLine();
gen.writeln("] def");
gen.writeDSCComment(DSCConstants.END_RESOURCE);
gen.getResourceTracker().registerSuppliedResource(res);
return res;
}
/**
* Derives a new font based on an existing font with a given encoding. The encoding must
* have been registered before.
* @param gen the PostScript generator
* @param baseFontName the font name of the font to derive from
* @param fontName the font name of the new font to be define
* @param encoding the new encoding (must be predefined in the PS file)
* @return the PSResource representing the derived font
* @throws IOException In case of an I/O problem
*/
public static PSResource defineDerivedFont(
PSGenerator gen, String baseFontName, String fontName, String encoding)
throws IOException {
PSResource res = new PSResource(PSResource.TYPE_FONT, fontName);
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res);
gen.commentln("%XGCDependencies: font " + baseFontName);
gen.commentln("%XGC+ encoding " + encoding);
gen.writeln("/" + baseFontName + " findfont");
gen.writeln("dup length dict begin");
gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall");
gen.writeln(" /Encoding " + encoding + " def");
gen.writeln(" currentdict");
gen.writeln("end");
gen.writeln("/" + fontName + " exch definefont pop");
gen.writeDSCComment(DSCConstants.END_RESOURCE);
gen.getResourceTracker().registerSuppliedResource(res);
return res;
}
private static PSResource defineDerivedTrueTypeFont(PSGenerator gen,
PSEventProducer eventProducer, String baseFontName, String fontName,
SingleByteEncoding encoding, CMapSegment[] cmap) throws IOException {
checkPostScriptLevel3(gen, eventProducer, "TrueType");
PSResource res = new PSResource(PSResource.TYPE_FONT, fontName);
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res);
gen.commentln("%XGCDependencies: font " + baseFontName);
gen.commentln("%XGC+ encoding " + encoding.getName());
gen.writeln("/" + baseFontName + " findfont");
gen.writeln("dup length dict begin");
gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall");
gen.writeln(" /Encoding " + encoding.getName() + " def");
gen.writeln(" /CharStrings 256 dict dup begin");
String[] charNameMap = encoding.getCharNameMap();
char[] unicodeCharMap = encoding.getUnicodeCharMap();
assert charNameMap.length == unicodeCharMap.length;
for (int i = 0; i < charNameMap.length; i++) {
String glyphName = charNameMap[i];
gen.write(" /");
gen.write(glyphName);
gen.write(" ");
if (glyphName.equals(".notdef")) {
gen.write(0);
} else {
gen.write(getGlyphIndex(unicodeCharMap[i], cmap));
}
gen.writeln(" def");
}
gen.writeln(" end readonly def");
gen.writeln(" currentdict");
gen.writeln("end");
gen.writeln("/" + fontName + " exch definefont pop");
gen.writeDSCComment(DSCConstants.END_RESOURCE);
gen.getResourceTracker().registerSuppliedResource(res);
return res;
}
}