/* * 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.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; 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.CFFToType1Font; 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. */ // @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") 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); PSFontResource fontResource = embedFont(gen, tf, 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 (Map.Entry<String, Typeface> e : fonts.entrySet()) { String key = e.getKey(); Typeface tf = e.getValue(); 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, PSEventProducer eventProducer) throws IOException { boolean embeddedFont = false; FontType fontType = tf.getFontType(); PSFontResource fontResource = null; PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName()); if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE || fontType == FontType.TYPE0 || fontType == FontType.TYPE1C) || !(tf instanceof CustomFont)) { gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); fontResource = PSFontResource.createFontResource(fontRes); return fontResource; } CustomFont cf = (CustomFont)tf; if (isEmbeddable(cf)) { List<InputStream> ins = getInputStreamOnFont(gen, cf); if (ins != null) { int i = 0; for (InputStream in : ins) { if (i > 0) { fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName() + "." + i); } if (fontType == FontType.TYPE0 || fontType == FontType.TYPE1C) { 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, (CustomFont) tf, in); if (fontResource == null) { fontResource = PSFontResource.createFontResource(fontRes); } } else if (fontType == FontType.TRUETYPE) { embedTrueTypeFont(gen, (SingleByteFont) tf, in); fontResource = PSFontResource.createFontResource(fontRes); } else if (!((MultiByteFont) tf).isOTFFile()) { composeType0Font(gen, (MultiByteFont) tf); } gen.writeDSCComment(DSCConstants.END_RESOURCE); gen.getResourceTracker().registerSuppliedResource(fontRes); embeddedFont = true; i++; } } 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; } 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, CustomFont 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, (SingleByteFont) font); fontStream = new ByteArrayInputStream(byteSubset); } embedType1Font(gen, fontStream); if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) { writeEncoding(gen, (SingleByteFont) 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) 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 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, 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 List<InputStream> getInputStreamOnFont(PSGenerator gen, CustomFont font) throws IOException { if (isEmbeddable(font)) { List<InputStream> fonts = new ArrayList<InputStream>(); InputStream in = font.getInputStream(); if (in == null) { if (font instanceof CFFToType1Font) { return ((CFFToType1Font) font).getInputStreams(); } return null; } //Make sure the InputStream is decorated with a BufferedInputStream if (!(in instanceof java.io.BufferedInputStream)) { in = new java.io.BufferedInputStream(in); } fonts.add(in); return fonts; } 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; } }