| /* |
| * 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.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import javax.xml.transform.Source; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| 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.CustomFont; |
| 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.SingleByteEncoding; |
| import org.apache.fop.fonts.SingleByteFont; |
| import org.apache.fop.fonts.Typeface; |
| |
| /** |
| * Utility code for font handling in PostScript. |
| */ |
| public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { |
| |
| /** logging instance */ |
| protected static 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, fontInfo.getFonts(), true); |
| } |
| |
| /** |
| * 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 |
| * @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) |
| throws IOException { |
| return writeFontDict(gen, fontInfo, fonts, false); |
| } |
| |
| /** |
| * 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) throws IOException { |
| gen.commentln("%FOPBeginFontDict"); |
| |
| Map fontResources = new java.util.HashMap(); |
| Iterator iter = fonts.keySet().iterator(); |
| while (iter.hasNext()) { |
| String key = (String)iter.next(); |
| Typeface tf = getTypeFace(fontInfo, fonts, key); |
| PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getFontName()); |
| fontResources.put(key, fontRes); |
| embedFont(gen, tf, fontRes); |
| |
| 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 = defineDerivedFont(gen, tf.getFontName(), |
| tf.getFontName() + postFix, encoding.getName()); |
| fontResources.put(key + postFix, 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 |
| defineWinAnsiEncoding(gen); |
| } |
| gen.commentln("%FOPBeginFontReencode"); |
| |
| //Rewrite font encodings |
| Iterator iter = fonts.keySet().iterator(); |
| while (iter.hasNext()) { |
| String key = (String)iter.next(); |
| Typeface tf = (Typeface)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.getFontName(), 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.getFontName(), tf.getEncodingName()); |
| } |
| } |
| } |
| } |
| gen.commentln("%FOPEndFontReencode"); |
| } |
| |
| private static Typeface getTypeFace(FontInfo fontInfo, Map fonts, String key) { |
| Typeface tf = (Typeface)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 = (Typeface)fonts.get(fallbackKey); |
| } |
| return tf; |
| } |
| |
| /** |
| * Embeds a font in the PostScript file. |
| * @param gen the PostScript generator |
| * @param tf the font |
| * @param fontRes the PSResource associated with the font |
| * @throws IOException In case of an I/O error |
| */ |
| public static void embedFont(PSGenerator gen, Typeface tf, PSResource fontRes) |
| throws IOException { |
| boolean embeddedFont = false; |
| if (FontType.TYPE1 == tf.getFontType()) { |
| if (tf instanceof CustomFont) { |
| CustomFont cf = (CustomFont)tf; |
| if (isEmbeddable(cf)) { |
| InputStream in = getInputStreamOnFont(gen, cf); |
| if (in != null) { |
| gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, |
| fontRes); |
| embedType1Font(gen, in); |
| gen.writeDSCComment(DSCConstants.END_RESOURCE); |
| gen.getResourceTracker().registerSuppliedResource(fontRes); |
| embeddedFont = true; |
| } else { |
| gen.commentln("%WARNING: Could not embed font: " + cf.getFontName()); |
| log.warn("Font " + cf.getFontName() + " is marked as supplied in the" |
| + " PostScript file but could not be embedded!"); |
| } |
| } |
| } |
| } |
| if (!embeddedFont) { |
| gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); |
| } |
| } |
| |
| private static boolean isEmbeddable(CustomFont font) { |
| return font.isEmbeddable(); |
| } |
| |
| private static InputStream getInputStreamOnFont(PSGenerator gen, CustomFont font) |
| throws IOException { |
| if (isEmbeddable(font)) { |
| Source source = font.getEmbedFileSource(); |
| if (source == null && font.getEmbedResourceName() != null) { |
| source = new StreamSource(PSFontUtils.class |
| .getResourceAsStream(font.getEmbedResourceName())); |
| } |
| if (source == null) { |
| return null; |
| } |
| InputStream in = null; |
| if (source instanceof StreamSource) { |
| in = ((StreamSource) source).getInputStream(); |
| } |
| if (in == null && source.getSystemId() != null) { |
| try { |
| in = new java.net.URL(source.getSystemId()).openStream(); |
| } catch (MalformedURLException e) { |
| new FileNotFoundException( |
| "File not found. URL could not be resolved: " |
| + e.getMessage()); |
| } |
| } |
| 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 fonts) { |
| Map fontResources = new java.util.HashMap(); |
| Iterator iter = fonts.keySet().iterator(); |
| while (iter.hasNext()) { |
| String key = (String)iter.next(); |
| Typeface tf = getTypeFace(fontInfo, fonts, key); |
| PSResource fontRes = new PSResource("font", tf.getFontName()); |
| fontResources.put(key, fontRes); |
| if (FontType.TYPE1 == tf.getFontType()) { |
| if (tf instanceof CustomFont) { |
| CustomFont cf = (CustomFont)tf; |
| if (isEmbeddable(cf)) { |
| 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.getFontName() + "_" + (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; |
| } |
| |
| } |