org.apache.fop.render.ps.PSFontUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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: PSFontUtils.java 1812122 2017-10-13 12:12:52Z ssteiner $ */
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.java2d.GeneralGraphics2DImagePainter;
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.FontTriplet;
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 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 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 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 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 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 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 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 glyphNames = new HashSet();
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 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 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 getInputStreamOnFont(PSGenerator gen, CustomFont font)
throws IOException {
if (isEmbeddable(font)) {
List fonts = new ArrayList();
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 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;
}
public static void addFallbackFonts(FontInfo fontInfo, GeneralGraphics2DImagePainter painter) throws IOException {
for (Map.Entry x : fontInfo.getFontTriplets().entrySet()) {
String name = x.getKey().getName();
Typeface typeface = fontInfo.getFonts().get(x.getValue());
painter.addFallbackFont(name, typeface);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy