From: Jeremias Maerki Date: Thu, 14 Feb 2008 08:12:34 +0000 (+0000) Subject: Added support for Type 1 fonts which don't use the AdobeStandardEncoding for PDF... X-Git-Tag: fop-0_95beta~70 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=c29ce0ae80329a756e17c263c1c4886d50708b3f;p=xmlgraphics-fop.git Added support for Type 1 fonts which don't use the AdobeStandardEncoding for PDF and PS output. Details: Added an Type 1 AFM parser (only basic ltr script fonts are properly supported). Font loading changed slightly to allow loading an AFM in addition to a PFM. Added some mapping functionality to CodePointMapping. Now we also build custom CodePointMapping instances from AFM files and use it in SingleByteFonts. Changed more PDF object classes to make use of the generic PDFDictionary and PDFArray base classes. Type 1 Fonts with a special encoding now register their encoding in the Encoding value of the font dictionary so the mapping is correct. For PS this isn't necessary as the interpreter just uses the font's default encoding. Refactored CMap building code to it can also be used outside the PDF context. A CMap can now also be built from a single byte encoding. Update of XML Graphics Commons snapshot. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@627679 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/lib/xmlgraphics-commons-1.3svn.jar b/lib/xmlgraphics-commons-1.3svn.jar index 0abad1c33..80927dd7e 100644 Binary files a/lib/xmlgraphics-commons-1.3svn.jar and b/lib/xmlgraphics-commons-1.3svn.jar differ diff --git a/src/codegen/fonts/code-point-mapping.xsl b/src/codegen/fonts/code-point-mapping.xsl index 53636505d..80b62dd17 100644 --- a/src/codegen/fonts/code-point-mapping.xsl +++ b/src/codegen/fonts/code-point-mapping.xsl @@ -40,23 +40,37 @@ package org.apache.fop.fonts; +import java.util.Arrays; import java.util.Map; import java.util.Collections; +import org.apache.fop.util.CharUtilities; + public class CodePointMapping { + + + + private String name; private char[] latin1Map; private char[] characters; private char[] codepoints; + private char[] unicodeMap; //code point to Unicode char - private CodePointMapping(int [] table) { + public CodePointMapping(String name, int[] table) { + this.name = name; int nonLatin1 = 0; latin1Map = new char[256]; + unicodeMap = new char[256]; + Arrays.fill(unicodeMap, CharUtilities.NOT_A_CHARACTER); for (int i = 0; i < table.length; i += 2) { if (table[i + 1] < 256) { latin1Map[table[i + 1]] = (char) table[i]; } else { ++nonLatin1; } + if (unicodeMap[table[i]] == CharUtilities.NOT_A_CHARACTER) { + unicodeMap[table[i]] = (char)table[i + 1]; + } } characters = new char[nonLatin1]; codepoints = new char[nonLatin1]; @@ -79,6 +93,10 @@ public class CodePointMapping { } } + public String getName() { + return this.name; + } + public final char mapChar(char c) { if (c < 256) { return latin1Map[c]; @@ -100,10 +118,26 @@ public class CodePointMapping { } } + public final char getUnicodeForIndex(int idx) { + return this.unicodeMap[idx]; + } + + public final char[] getUnicodeCharMap() { + char[] copy = new char[this.unicodeMap.length]; + System.arraycopy(this.unicodeMap, 0, copy, 0, this.unicodeMap.length); + return copy; + } + + /** {@inheritDoc} */ + public String toString() { + return getName(); + } + private static Map mappings; static { mappings = Collections.synchronizedMap(new java.util.HashMap()); } + public static CodePointMapping getMapping(String encoding) { CodePointMapping mapping = (CodePointMapping) mappings.get(encoding); if (mapping != null) { @@ -119,10 +153,12 @@ public class CodePointMapping { } + public static final String = ""; + - else if (encoding.equals("")) { - mapping = new CodePointMapping(enc); - mappings.put("", mapping); + else if (encoding.equals()) { + mapping = new CodePointMapping(, enc); + mappings.put(, mapping); return mapping; } diff --git a/src/codegen/fonts/encodings.xml b/src/codegen/fonts/encodings.xml index 1f8497ee3..d7855000e 100644 --- a/src/codegen/fonts/encodings.xml +++ b/src/codegen/fonts/encodings.xml @@ -19,12 +19,12 @@ - + ]> - + @@ -175,7 +175,7 @@ - + @@ -377,7 +377,7 @@ - + @@ -594,7 +594,7 @@ - + @@ -803,7 +803,7 @@ - + @@ -1021,7 +1021,7 @@ - + @@ -1252,7 +1252,7 @@ - + @@ -1443,7 +1443,7 @@ - + diff --git a/src/java/org/apache/fop/fonts/CIDFontType.java b/src/java/org/apache/fop/fonts/CIDFontType.java index 4fa3597b2..51b4a73d1 100644 --- a/src/java/org/apache/fop/fonts/CIDFontType.java +++ b/src/java/org/apache/fop/fonts/CIDFontType.java @@ -27,12 +27,12 @@ import org.apache.avalon.framework.ValuedEnum; public class CIDFontType extends ValuedEnum { /** - * CID Font Type 0 + * CID Font Type 0 (based on Type 1 format) */ public static final CIDFontType CIDTYPE0 = new CIDFontType("CIDFontType0", 0); /** - * CID Font Type 2 + * CID Font Type 2 (based on TrueType format) */ public static final CIDFontType CIDTYPE2 = new CIDFontType("CIDFontType2", 1); diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java index f165fbd67..0ac3b80bc 100644 --- a/src/java/org/apache/fop/fonts/CustomFont.java +++ b/src/java/org/apache/fop/fonts/CustomFont.java @@ -230,9 +230,7 @@ public abstract class CustomFont extends Typeface * @return the index of the first character */ public int getFirstChar() { - return 0; - // return firstChar; - /**(todo) Why is this hardcoded??? This code was in SingleByteFont.java */ + return firstChar; } /** @@ -408,14 +406,25 @@ public abstract class CustomFont extends Typeface this.resolver = resolver; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void putKerningEntry(Integer key, Map value) { if (kerning == null) { kerning = new java.util.HashMap(); } this.kerning.put(key, value); } + + /** + * Replaces the existing kerning map with a new one. + * @param kerningMap the kerning map (Map, the integers are + * character codes) + */ + public void replaceKerningMap(Map kerningMap) { + if (kerningMap == null) { + this.kerning = Collections.EMPTY_MAP; + } else { + this.kerning = kerningMap; + } + } } diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java index 8b682f1ae..1fa328a0a 100644 --- a/src/java/org/apache/fop/fonts/FontLoader.java +++ b/src/java/org/apache/fop/fonts/FontLoader.java @@ -28,9 +28,9 @@ import java.net.URL; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; -import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.fonts.truetype.TTFFontLoader; import org.apache.fop.fonts.type1.Type1FontLoader; @@ -46,8 +46,6 @@ public abstract class FontLoader { /** URI representing the font file */ protected String fontFileURI = null; - /** the InputStream to load the font from */ - protected InputStream in = null; /** the FontResolver to use for font URI resolution */ protected FontResolver resolver = null; /** the loaded font */ @@ -59,12 +57,10 @@ public abstract class FontLoader { /** * Default constructor. * @param fontFileURI the URI to the PFB file of a Type 1 font - * @param in the InputStream reading the PFM file of a Type 1 font * @param resolver the font resolver used to resolve URIs */ - public FontLoader(String fontFileURI, InputStream in, FontResolver resolver) { + public FontLoader(String fontFileURI, FontResolver resolver) { this.fontFileURI = fontFileURI; - this.in = in; this.resolver = resolver; } @@ -107,58 +103,25 @@ public abstract class FontLoader { public static CustomFont loadFont(String fontFileURI, FontResolver resolver) throws IOException { fontFileURI = fontFileURI.trim(); - String effURI; boolean type1 = isType1(fontFileURI); + FontLoader loader; if (type1) { - String pfmExt = fontFileURI.substring( - fontFileURI.length() - 3, fontFileURI.length()); - pfmExt = pfmExt.substring(0, 2) + (Character.isUpperCase(pfmExt.charAt(2)) ? "M" : "m"); - effURI = fontFileURI.substring(0, fontFileURI.length() - 4) + "." + pfmExt; + loader = new Type1FontLoader(fontFileURI, resolver); } else { - effURI = fontFileURI; + loader = new TTFFontLoader(fontFileURI, resolver); } - if (log.isDebugEnabled()) { - log.debug("opening " + effURI); - } - InputStream in = openFontUri(resolver, effURI); - return loadFontFromInputStream(fontFileURI, resolver, type1, in); + return loader.getFont(); } /** - * Loads and returns a font given an input stream. - * @param fontFileURI font file uri - * @param resolver font resolver - * @param isType1 is it a type1 font? - * @param in input stream - * @return the loaded font. - * @throws IOException In case of an I/O error - */ - protected static CustomFont loadFontFromInputStream( - String fontFileURI, FontResolver resolver, boolean isType1, - InputStream in) - throws IOException { - FontLoader loader; - try { - if (isType1) { - loader = new Type1FontLoader(fontFileURI, in, resolver); - } else { - loader = new TTFFontLoader(fontFileURI, in, resolver); - } - return loader.getFont(); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Opens a font uri and returns an input stream. + * Opens a font URI and returns an input stream. * @param resolver the FontResolver to use for font URI resolution * @param uri the URI representing the font * @return the InputStream to read the font from. * @throws IOException In case of an I/O error * @throws MalformedURLException If an invalid URL is built */ - private static InputStream openFontUri(FontResolver resolver, String uri) + protected static InputStream openFontUri(FontResolver resolver, String uri) throws IOException, MalformedURLException { InputStream in = null; if (resolver != null) { @@ -191,7 +154,11 @@ public abstract class FontLoader { */ protected abstract void read() throws IOException; - /** @see FontLoader#getFont() */ + /** + * Returns the custom font that was read using this instance of FontLoader. + * @return the newly loaded font + * @throws IOException if an I/O error occurs + */ public CustomFont getFont() throws IOException { if (!loaded) { read(); diff --git a/src/java/org/apache/fop/fonts/FontType.java b/src/java/org/apache/fop/fonts/FontType.java index a00721ebf..50ca2c8a6 100644 --- a/src/java/org/apache/fop/fonts/FontType.java +++ b/src/java/org/apache/fop/fonts/FontType.java @@ -31,7 +31,7 @@ public class FontType extends ValuedEnum { */ public static final FontType OTHER = new FontType("Other", 0); /** - * Adobe Type 0 fonts + * Adobe Type 0 fonts (composite font) */ public static final FontType TYPE0 = new FontType("Type0", 1); /** diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java index 1df44b3a2..b6f65edc8 100644 --- a/src/java/org/apache/fop/fonts/SingleByteFont.java +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -19,6 +19,8 @@ package org.apache.fop.fonts; +import java.util.Set; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,92 +34,113 @@ public class SingleByteFont extends CustomFont { private CodePointMapping mapping; - private String encoding = "WinAnsiEncoding"; - private int[] width = null; + private Set warnedChars; + /** * Main constructor. */ public SingleByteFont() { - updateMapping(); + setEncoding(CodePointMapping.WIN_ANSI_ENCODING); } - /** - * Updates the mapping variable based on the encoding. - */ - protected void updateMapping() { - try { - mapping = CodePointMapping.getMapping(getEncoding()); - } catch (UnsupportedOperationException e) { - log.error("Font '" + super.getFontName() + "': " + e.getMessage()); - } - } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean isEmbeddable() { return (getEmbedFileName() == null && getEmbedResourceName() == null) ? false : true; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public String getEncoding() { - return encoding; + return this.mapping.getName(); } /** - * Sets the encoding of the font. - * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding") + * Returns the code point mapping (encoding) of this font. + * @return the code point mapping */ - public void setEncoding(String encoding) { - this.encoding = encoding; - updateMapping(); + public CodePointMapping getCodePointMapping() { + return this.mapping; } - - /** - * {@inheritDoc} - */ + + /** {@inheritDoc} */ public int getWidth(int i, int size) { - return size * width[i]; + int idx = i - getFirstChar(); + if (idx >= 0 && idx < width.length) { + return size * width[i - getFirstChar()]; + } else { + return 0; + } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int[] getWidths() { int[] arr = new int[width.length]; System.arraycopy(width, 0, arr, 0, width.length - 1); return arr; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public char mapChar(char c) { notifyMapOperation(); char d = mapping.mapChar(c); if (d != 0) { return d; } else { - log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c) - + ") not available in font " + getFontName()); + Character ch = new Character(c); + if (warnedChars == null) { + warnedChars = new java.util.HashSet(); + } + if (warnedChars.size() < 8 && !warnedChars.contains(ch)) { + warnedChars.add(ch); + if (warnedChars.size() == 8) { + log.warn("Many requested glyphs are not available in font " + getFontName()); + } else { + log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c) + + ", " + Glyphs.charToGlyphName(c) + + ") not available in font " + getFontName()); + } + } return '#'; } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean hasChar(char c) { return (mapping.mapChar(c) > 0); } /* ---- single byte font specific setters --- */ + /** + * Updates the mapping variable based on the encoding. + * @param encoding the name of the encoding + */ + protected void updateMapping(String encoding) { + try { + this.mapping = CodePointMapping.getMapping(encoding); + } catch (UnsupportedOperationException e) { + log.error("Font '" + super.getFontName() + "': " + e.getMessage()); + } + } + + /** + * Sets the encoding of the font. + * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding") + */ + public void setEncoding(String encoding) { + updateMapping(encoding); + } + + /** + * Sets the encoding of the font. + * @param encoding the encoding information + */ + public void setEncoding(CodePointMapping encoding) { + this.mapping = encoding; + } + /** * Sets a width for a character. * @param index index of the character @@ -125,9 +148,9 @@ public class SingleByteFont extends CustomFont { */ public void setWidth(int index, int width) { if (this.width == null) { - this.width = new int[256]; + this.width = new int[getLastChar() - getFirstChar() + 1]; } - this.width[index] = width; + this.width[index - getFirstChar()] = width; } } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java index 58267571e..5c9e51c81 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java @@ -25,6 +25,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.commons.io.IOUtils; + import org.apache.fop.fonts.BFEntry; import org.apache.fop.fonts.CIDFontType; import org.apache.fop.fonts.FontLoader; @@ -41,23 +43,30 @@ public class TTFFontLoader extends FontLoader { /** * Default constructor * @param fontFileURI the URI representing the font file - * @param in the InputStream to load the font from * @param resolver the FontResolver for font URI resolution */ - public TTFFontLoader(String fontFileURI, InputStream in, FontResolver resolver) { - super(fontFileURI, in, resolver); + public TTFFontLoader(String fontFileURI, FontResolver resolver) { + super(fontFileURI, resolver); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void read() throws IOException { - TTFFile ttf = new TTFFile(); - FontFileReader reader = new FontFileReader(in); - boolean supported = ttf.readFont(reader, null); - if (!supported) { - throw new IOException("Could not load TrueType font: " + fontFileURI); + InputStream in = openFontUri(resolver, this.fontFileURI); + try { + TTFFile ttf = new TTFFile(); + FontFileReader reader = new FontFileReader(in); + boolean supported = ttf.readFont(reader, null); + if (!supported) { + throw new IOException("Could not load TrueType font: " + fontFileURI); + } + buildFont(ttf); + loaded = true; + } finally { + IOUtils.closeQuietly(in); } + } + + private void buildFont(TTFFile ttf) { if (ttf.isCFF()) { throw new UnsupportedOperationException( "OpenType fonts with CFF data are not supported, yet"); @@ -98,7 +107,6 @@ public class TTFFontLoader extends FontLoader { multiFont.setBFEntries(bfentries); copyKerning(ttf, true); multiFont.setEmbedFileName(this.fontFileURI); - loaded = true; } /** diff --git a/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java b/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java new file mode 100644 index 000000000..918d9ee08 --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + + +/** + * Holds the metrics of a single character from an AFM file. + */ +public class AFMCharMetrics { + + private int charCode; + private String unicodeChars; + private String charName; + private double widthX; + private double widthY; + + /** + * Returns the character code. + * @return the charCode + */ + public int getCharCode() { + return charCode; + } + + /** + * Sets the character code. + * @param charCode the charCode to set + */ + public void setCharCode(int charCode) { + this.charCode = charCode; + } + + /** + * Returns the Unicode characters represented by this object. Some character names can be + * mapped to multiple Unicode code points, so expect to find more than one character in the + * String. + * @return the Unicode characters + */ + public String getUnicodeChars() { + return this.unicodeChars; + } + + /** + * Sets the Unicode characters represented by this object. + * @param unicodeChars the Unicode characters + */ + public void setUnicodeChars(String unicodeChars) { + this.unicodeChars = unicodeChars; + } + + /** + * Returns the PostScript character name. + * @return the charName + */ + public String getCharName() { + return charName; + } + + /** + * Sets the PostScript character name. + * @param charName the charName to set + */ + public void setCharName(String charName) { + this.charName = charName; + } + + /** + * Returns the progression dimension in x-direction. + * @return the widthX + */ + public double getWidthX() { + return widthX; + } + + /** + * Sets the progression dimension in x-direction + * @param widthX the widthX to set + */ + public void setWidthX(double widthX) { + this.widthX = widthX; + } + + /** + * Returns the progression dimension in y-direction. + * @return the widthY + */ + public double getWidthY() { + return widthY; + } + + /** + * Sets the progression dimension in y-direction + * @param widthY the widthY to set + */ + public void setWidthY(double widthY) { + this.widthY = widthY; + } + + /** {@inheritDoc} */ + public String toString() { + StringBuffer sb = new StringBuffer("AFM Char: "); + sb.append(getCharCode()); + sb.append(" ("); + if (getUnicodeChars() != null) { + for (int i = 0, c = getUnicodeChars().length(); i < c; i++) { + sb.append("0x").append(Integer.toHexString(getUnicodeChars().charAt(i))); + sb.append(", "); + } + } + sb.append(getCharName()).append(')'); + return sb.toString(); + } + +} diff --git a/src/java/org/apache/fop/fonts/type1/AFMFile.java b/src/java/org/apache/fop/fonts/type1/AFMFile.java new file mode 100644 index 000000000..af912975c --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/AFMFile.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +import java.awt.geom.RectangularShape; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.xmlgraphics.fonts.Glyphs; +import org.apache.xmlgraphics.java2d.Dimension2DDouble; + +/** + * Represents the contents of a Type 1 AFM font metrics file. + */ +public class AFMFile { + + private String fontName; + private String fullName; + private String familyName; + + private String weight; + private RectangularShape fontBBox; + + private String encodingScheme; + private String characterSet; + + private Number capHeight; + private Number xHeight; + private Number ascender; + private Number descender; + private Number stdHW; + private Number stdVW; + + private AFMWritingDirectionMetrics[] writingDirectionMetrics + = new AFMWritingDirectionMetrics[3]; + + private List charMetrics = new java.util.ArrayList(); + //List + private Map charNameToMetrics = new java.util.HashMap(); + //Map + + private Map kerningMap; + //Map> + + /** + * Default constructor. + */ + public AFMFile() { + //nop + } + + /** + * Returns the FontName value. + * @return the font name + */ + public String getFontName() { + return fontName; + } + + /** + * Sets the FontName value. + * @param fontName the font name to set + */ + public void setFontName(String fontName) { + this.fontName = fontName; + } + + /** + * Returns the FullName value. + * @return the full name of the font + */ + public String getFullName() { + return fullName; + } + + /** + * Sets the FullName value. + * @param fullName the full name to set + */ + public void setFullName(String fullName) { + this.fullName = fullName; + } + + /** + * Returns the FamilyName value. + * @return the family name of the font + */ + public String getFamilyName() { + return familyName; + } + + /** + * Sets the FamilyName value. + * @param familyName the family name to set + */ + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + /** + * Returns the Weight value. + * @return the weight + */ + public String getWeight() { + return weight; + } + + /** + * Sets the Weight value. + * @param weight the weight to set + */ + public void setWeight(String weight) { + this.weight = weight; + } + + /** + * Returns the FontBBox value. + * @return the font's bounding box + */ + public RectangularShape getFontBBox() { + return fontBBox; + } + + /** + * Returns the FontBBox value as integer array. + * @return the font's bounding box + */ + public int[] getFontBBoxAsIntArray() { + RectangularShape rect = getFontBBox(); + return new int[] { + (int)Math.floor(rect.getMinX()), (int)Math.floor(rect.getMinY()), + (int)Math.ceil(rect.getMaxX()), (int)Math.ceil(rect.getMaxY())}; + } + + /** + * Sets the FontBBox value. + * @param fontBBox the fontBBox to set + */ + public void setFontBBox(RectangularShape fontBBox) { + this.fontBBox = fontBBox; + } + + /** + * Returns the EncodingScheme value. + * @return the encoding scheme + */ + public String getEncodingScheme() { + return encodingScheme; + } + + /** + * Sets the EncodingScheme value + * @param encodingScheme the encodingScheme to set + */ + public void setEncodingScheme(String encodingScheme) { + this.encodingScheme = encodingScheme; + } + + /** + * Returns the CharacterSet value. + * @return the characterSet + */ + public String getCharacterSet() { + return characterSet; + } + + /** + * Sets the CharacterSet value. + * @param characterSet the characterSet to set + */ + public void setCharacterSet(String characterSet) { + this.characterSet = characterSet; + } + + /** + * Returns the CapHeight value. + * @return the capHeight + */ + public Number getCapHeight() { + return capHeight; + } + + /** + * Sets the CapHeight value. + * @param capHeight the capHeight to set + */ + public void setCapHeight(Number capHeight) { + this.capHeight = capHeight; + } + + /** + * Returns the XHeight value. + * @return the xHeight + */ + public Number getXHeight() { + return xHeight; + } + + /** + * Sets the XHeight value. + * @param height the xHeight to set + */ + public void setXHeight(Number height) { + xHeight = height; + } + + /** + * Returns the Ascender value. + * @return the ascender + */ + public Number getAscender() { + return ascender; + } + + /** + * Sets the Ascender value. + * @param ascender the ascender to set + */ + public void setAscender(Number ascender) { + this.ascender = ascender; + } + + /** + * Returns the Descender value. + * @return the descender + */ + public Number getDescender() { + return descender; + } + + /** + * Sets the Descender value. + * @param descender the descender to set + */ + public void setDescender(Number descender) { + this.descender = descender; + } + + /** + * Returns the StdHW value. + * @return the descender + */ + public Number getStdHW() { + return stdHW; + } + + /** + * Sets the StdHW value. + * @param stdHW the StdHW to set + */ + public void setStdHW(Number stdHW) { + this.stdHW = stdHW; + } + + /** + * Returns the StdVW value. + * @return the descender + */ + public Number getStdVW() { + return stdVW; + } + + /** + * Sets the StdVW value. + * @param stdVW the StdVW to set + */ + public void setStdVW(Number stdVW) { + this.stdVW = stdVW; + } + + /** + * Gets writing direction metrics. + * @param index the writing direction (0, 1 or 2) + * @return the writing direction metrics + */ + public AFMWritingDirectionMetrics getWritingDirectionMetrics(int index) { + return this.writingDirectionMetrics[index]; + } + + /** + * Sets writing direction metrics. + * @param index the writing direction (0, 1 or 2) + * @param metrics the writing direction metrics + */ + public void setWritingDirectionMetrics(int index, AFMWritingDirectionMetrics metrics) { + this.writingDirectionMetrics[index] = metrics; + } + + /** + * Adds new character metrics. + * @param metrics the character metrics + */ + public void addCharMetrics(AFMCharMetrics metrics) { + String name = metrics.getCharName(); + if (metrics.getUnicodeChars() == null) { + if (name != null) { + String u = Glyphs.getUnicodeCodePointsForGlyphName(metrics.getCharName()); + if (u != null) { + metrics.setUnicodeChars(u); + } + } else { + //Ignore as no Unicode assignment is possible + return; + } + } + this.charMetrics.add(metrics); + if (name != null) { + this.charNameToMetrics.put(name, metrics); + } + } + + /** + * Returns the number of character available for this font. + * @return the number of character + */ + public int getCharCount() { + return this.charMetrics.size(); + } + + /** + * Returns the character metrics associated with the character name. + * @param name the character name + * @return the character metrics or null if there's no such character + */ + public AFMCharMetrics getChar(String name) { + return (AFMCharMetrics)this.charNameToMetrics.get(name); + } + + /** + * Returns the list of AFMCharMetrics instances representing all the available characters. + * @return a List of AFMCharMetrics instances + */ + public List getCharMetrics() { + return Collections.unmodifiableList(this.charMetrics); + } + + /** + * Adds a X-kerning entry. + * @param name1 the name of the first character + * @param name2 the name of the second character + * @param kx kerning value in x-direction + */ + public void addXKerning(String name1, String name2, double kx) { + if (this.kerningMap == null) { + this.kerningMap = new java.util.HashMap(); + } + Map entries = (Map)this.kerningMap.get(name1); + if (entries == null) { + entries = new java.util.HashMap(); + this.kerningMap.put(name1, entries); + } + entries.put(name2, new Dimension2DDouble(kx, 0)); + } + + /** {@inheritDoc} */ + public String toString() { + return "AFM: " + getFullName(); + } + +} diff --git a/src/java/org/apache/fop/fonts/type1/AFMParser.java b/src/java/org/apache/fop/fonts/type1/AFMParser.java new file mode 100644 index 000000000..4d852058c --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/AFMParser.java @@ -0,0 +1,594 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +import java.awt.Rectangle; +import java.beans.Statement; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Map; +import java.util.Stack; + +import org.apache.commons.io.IOUtils; + +/** + * Parses the contents of a Type 1 AFM font metrics file into an object structure ({@link AFMFile}). + */ +public class AFMParser { + + private static final String START_FONT_METRICS = "StartFontMetrics"; + //private static final String END_FONT_METRICS = "EndFontMetrics"; + private static final String FONT_NAME = "FontName"; + private static final String FULL_NAME = "FullName"; + private static final String FAMILY_NAME = "FamilyName"; + private static final String WEIGHT = "Weight"; + private static final String FONT_BBOX = "FontBBox"; + private static final String ENCODING_SCHEME = "EncodingScheme"; + private static final String CHARACTER_SET = "CharacterSet"; + private static final String IS_BASE_FONT = "IsBaseFont"; + private static final String IS_CID_FONT = "IsCIDFont"; + private static final String CAP_HEIGHT = "CapHeight"; + private static final String X_HEIGHT = "XHeight"; + private static final String ASCENDER = "Ascender"; + private static final String DESCENDER = "Descender"; + private static final String STDHW = "StdHW"; + private static final String STDVW = "StdVW"; + private static final String UNDERLINE_POSITION = "UnderlinePosition"; + private static final String UNDERLINE_THICKNESS = "UnderlineThickness"; + private static final String ITALIC_ANGLE = "ItalicAngle"; + private static final String IS_FIXED_PITCH = "IsFixedPitch"; + private static final String START_DIRECTION = "StartDirection"; + private static final String END_DIRECTION = "EndDirection"; + private static final String START_CHAR_METRICS = "StartCharMetrics"; + private static final String END_CHAR_METRICS = "EndCharMetrics"; + private static final String C = "C"; + private static final String CH = "CH"; + private static final String WX = "WX"; + private static final String W0X = "W0X"; + private static final String W1X = "W1X"; + private static final String WY = "WY"; + private static final String W0Y = "W0Y"; + private static final String W1Y = "W1Y"; + private static final String W = "W"; + private static final String W0 = "W0"; + private static final String W1 = "W1"; + private static final String N = "N"; + private static final String START_TRACK_KERN = "StartTrackKern"; + private static final String END_TRACK_KERN = "EndTrackKern"; + //private static final String START_KERN_PAIRS = "StartKernPairs"; + //private static final String START_KERN_PAIRS0 = "StartKernPairs0"; + private static final String START_KERN_PAIRS1 = "StartKernPairs1"; + //private static final String END_KERN_PAIRS = "EndKernPairs"; + private static final String KP = "KP"; + private static final String KPH = "KPH"; + private static final String KPX = "KPX"; + private static final String KPY = "KPY"; + + private static final int PARSE_NORMAL = 0; + private static final int PARSE_CHAR_METRICS = 1; + + private static final Map VALUE_PARSERS; + private static final Map PARSE_MODE_CHANGES; + + static { + VALUE_PARSERS = new java.util.HashMap(); + VALUE_PARSERS.put(START_FONT_METRICS, new StartFontMetrics()); + VALUE_PARSERS.put(FONT_NAME, new StringSetter(FONT_NAME)); + VALUE_PARSERS.put(FULL_NAME, new StringSetter(FULL_NAME)); + VALUE_PARSERS.put(FAMILY_NAME, new StringSetter(FAMILY_NAME)); + VALUE_PARSERS.put(WEIGHT, new StringSetter(WEIGHT)); + VALUE_PARSERS.put(ENCODING_SCHEME, new StringSetter(ENCODING_SCHEME)); + VALUE_PARSERS.put(FONT_BBOX, new FontBBox()); + VALUE_PARSERS.put(CHARACTER_SET, new StringSetter(CHARACTER_SET)); + VALUE_PARSERS.put(IS_BASE_FONT, new IsBaseFont()); + VALUE_PARSERS.put(IS_CID_FONT, new IsCIDFont()); + VALUE_PARSERS.put(CAP_HEIGHT, new NumberSetter(CAP_HEIGHT)); + VALUE_PARSERS.put(X_HEIGHT, new NumberSetter(X_HEIGHT)); + VALUE_PARSERS.put(ASCENDER, new NumberSetter(ASCENDER)); + VALUE_PARSERS.put(DESCENDER, new NumberSetter(DESCENDER)); + VALUE_PARSERS.put(STDHW, new NumberSetter(STDHW)); + VALUE_PARSERS.put(STDVW, new NumberSetter(STDVW)); + VALUE_PARSERS.put(START_DIRECTION, new StartDirection()); + VALUE_PARSERS.put(END_DIRECTION, new EndDirection()); + VALUE_PARSERS.put(UNDERLINE_POSITION, new WritingDirNumberSetter(UNDERLINE_POSITION)); + VALUE_PARSERS.put(UNDERLINE_THICKNESS, new WritingDirNumberSetter(UNDERLINE_THICKNESS)); + VALUE_PARSERS.put(ITALIC_ANGLE, new WritingDirDoubleSetter(ITALIC_ANGLE)); + VALUE_PARSERS.put(IS_FIXED_PITCH, new WritingDirBooleanSetter(IS_FIXED_PITCH)); + VALUE_PARSERS.put(C, new IntegerSetter("CharCode")); + VALUE_PARSERS.put(CH, new NotImplementedYet(CH)); + VALUE_PARSERS.put(WX, new DoubleSetter("WidthX")); + VALUE_PARSERS.put(W0X, new DoubleSetter("WidthX")); + VALUE_PARSERS.put(W1X, new NotImplementedYet(W1X)); + VALUE_PARSERS.put(WY, new DoubleSetter("WidthY")); + VALUE_PARSERS.put(W0Y, new DoubleSetter("WidthY")); + VALUE_PARSERS.put(W1Y, new NotImplementedYet(W1Y)); + VALUE_PARSERS.put(W, new NotImplementedYet(W)); + VALUE_PARSERS.put(W0, new NotImplementedYet(W0)); + VALUE_PARSERS.put(W1, new NotImplementedYet(W1)); + VALUE_PARSERS.put(N, new StringSetter("CharName")); + VALUE_PARSERS.put(START_TRACK_KERN, new NotImplementedYet(START_TRACK_KERN)); + VALUE_PARSERS.put(END_TRACK_KERN, new NotImplementedYet(END_TRACK_KERN)); + VALUE_PARSERS.put(START_KERN_PAIRS1, new NotImplementedYet(START_KERN_PAIRS1)); + VALUE_PARSERS.put(KP, new NotImplementedYet(KP)); + VALUE_PARSERS.put(KPH, new NotImplementedYet(KPH)); + VALUE_PARSERS.put(KPX, new KPXHandler()); + VALUE_PARSERS.put(KPY, new NotImplementedYet(KPY)); + + PARSE_MODE_CHANGES = new java.util.HashMap(); + PARSE_MODE_CHANGES.put(START_CHAR_METRICS, new Integer(PARSE_CHAR_METRICS)); + PARSE_MODE_CHANGES.put(END_CHAR_METRICS, new Integer(PARSE_NORMAL)); + } + + /** + * Main constructor. + */ + public AFMParser() { + //nop + } + + /** + * Parses an AFM file from a local file. + * @param afmFile the AFM file + * @return the parsed AFM file + * @throws IOException if an I/O error occurs + */ + public AFMFile parse(File afmFile) throws IOException { + InputStream in = new java.io.FileInputStream(afmFile); + try { + return parse(in); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Parses an AFM file from a stream. + * @param in the stream to read from + * @return the parsed AFM file + * @throws IOException if an I/O error occurs + */ + public AFMFile parse(InputStream in) throws IOException { + Reader reader = new java.io.InputStreamReader(in, "US-ASCII"); + try { + return parse(new BufferedReader(reader)); + } finally { + IOUtils.closeQuietly(reader); + } + } + + /** + * Parses an AFM file from a BufferedReader. + * @param reader the BufferedReader instance to read from + * @return the parsed AFM file + * @throws IOException if an I/O error occurs + */ + public AFMFile parse(BufferedReader reader) throws IOException { + Stack stack = new Stack(); + int parseMode = PARSE_NORMAL; + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + String key = null; + switch (parseMode) { + case PARSE_NORMAL: + key = parseLine(line, stack); + break; + case PARSE_CHAR_METRICS: + key = parseCharMetrics(line, stack); + break; + default: + throw new IllegalStateException("Invalid parse mode"); + } + Integer newParseMode = (Integer)PARSE_MODE_CHANGES.get(key); + if (newParseMode != null) { + parseMode = newParseMode.intValue(); + } + } + return (AFMFile)stack.pop(); + } + + private String parseLine(String line, Stack stack) throws IOException { + int startpos = 0; + //Find key + startpos = skipToNonWhiteSpace(line, startpos); + int endpos = skipToWhiteSpace(line, startpos); + String key = line.substring(startpos, endpos); + + //Parse value + startpos = skipToNonWhiteSpace(line, endpos); + ValueHandler vp = (ValueHandler)VALUE_PARSERS.get(key); + if (vp != null) { + vp.parse(line, startpos, stack); + } + return key; + } + + private String parseCharMetrics(String line, Stack stack) throws IOException { + int startpos = 0; + AFMCharMetrics chm = new AFMCharMetrics(); + stack.push(chm); + while (true) { + //Find key + startpos = skipToNonWhiteSpace(line, startpos); + int endpos = skipToWhiteSpace(line, startpos); + String key = line.substring(startpos, endpos); + if (END_CHAR_METRICS.equals(key)) { + stack.pop(); //Pop and forget unused AFMCharMetrics instance + return key; + } else if (key.length() == 0) { + //EOL: No more key so break + break; + } + + //Extract value + startpos = skipToNonWhiteSpace(line, endpos); + endpos = skipToSemicolon(line, startpos); + String value = line.substring(startpos, endpos).trim(); + startpos = endpos + 1; + + //Parse value + ValueHandler vp = (ValueHandler)VALUE_PARSERS.get(key); + if (vp != null) { + vp.parse(value, 0, stack); + } + if (false) { + break; + } + } + stack.pop(); + AFMFile afm = (AFMFile)stack.peek(); + afm.addCharMetrics(chm); + return null; + } + + private static int skipToNonWhiteSpace(String line, int startpos) { + int pos = startpos; + while (pos < line.length() && isWhitespace(line.charAt(pos))) { + pos++; + } + return pos; + } + + private static int skipToWhiteSpace(String line, int startpos) { + int pos = startpos; + while (pos < line.length() && !isWhitespace(line.charAt(pos))) { + pos++; + } + return pos; + } + + private static int skipToSemicolon(String line, int startpos) { + int pos = startpos; + while (pos < line.length() && ';' != line.charAt(pos)) { + pos++; + } + return pos; + } + + private static boolean isWhitespace(char ch) { + return ch == ' ' + || ch == '\t'; + } + + // ---------------- Value Handlers --------------------------- + + private interface ValueHandler { + void parse(String line, int startpos, Stack stack) throws IOException; + } + + private abstract static class AbstractValueHandler implements ValueHandler { + + protected int findValue(String line, int startpos) { + return skipToWhiteSpace(line, startpos); + } + + protected String getStringValue(String line, int startpos) { + return line.substring(startpos); + } + + protected Number getNumberValue(String line, int startpos) { + try { + return new Integer(getIntegerValue(line, startpos)); + } catch (NumberFormatException nfe) { + return new Double(getDoubleValue(line, startpos)); + } + } + + protected int getIntegerValue(String line, int startpos) { + int endpos = findValue(line, startpos); + return Integer.parseInt(line.substring(startpos, endpos)); + } + + protected double getDoubleValue(String line, int startpos) { + int endpos = findValue(line, startpos); + return Double.parseDouble(line.substring(startpos, endpos)); + } + + protected Boolean getBooleanValue(String line, int startpos) { + return Boolean.valueOf(getStringValue(line, startpos)); + } + + } + + private static class StartFontMetrics extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + int endpos = findValue(line, startpos); + double version = Double.parseDouble(line.substring(startpos, endpos)); + if (version < 2) { + throw new IOException( + "AFM version must be at least 2.0 but it is " + version + "!"); + } + AFMFile afm = new AFMFile(); + stack.push(afm); + } + } + + private abstract static class BeanSetter extends AbstractValueHandler { + private String method; + + public BeanSetter(String variable) { + this.method = "set" + variable; + } + + protected void setValue(Object target, Object value) { + //Uses Java Beans API + Statement statement = new Statement(target, method, new Object[] {value}); + try { + statement.execute(); + } catch (Exception e) { + //Should never happen + throw new RuntimeException("Bean error: " + e.getMessage()); + } + } + } + + private static class StringSetter extends BeanSetter { + + public StringSetter(String variable) { + super(variable); + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + String s = getStringValue(line, startpos); + Object obj = stack.peek(); + setValue(obj, s); + } + } + + private static class NumberSetter extends BeanSetter { + public NumberSetter(String variable) { + super(variable); + } + + protected Object getContextObject(Stack stack) { + return stack.peek(); + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + Number num = getNumberValue(line, startpos); + setValue(getContextObject(stack), num); + } + } + + private static class IntegerSetter extends NumberSetter { + public IntegerSetter(String variable) { + super(variable); + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + int value = getIntegerValue(line, startpos); + setValue(getContextObject(stack), new Integer(value)); + } + } + + private static class DoubleSetter extends NumberSetter { + public DoubleSetter(String variable) { + super(variable); + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + double value = getDoubleValue(line, startpos); + setValue(getContextObject(stack), new Double(value)); + } + } + + private static class WritingDirNumberSetter extends NumberSetter { + + public WritingDirNumberSetter(String variable) { + super(variable); + } + + protected Object getContextObject(Stack stack) { + if (stack.peek() instanceof AFMWritingDirectionMetrics) { + return (AFMWritingDirectionMetrics)stack.peek(); + } else { + AFMFile afm = (AFMFile)stack.peek(); + AFMWritingDirectionMetrics wdm = afm.getWritingDirectionMetrics(0); + if (wdm == null) { + wdm = new AFMWritingDirectionMetrics(); + afm.setWritingDirectionMetrics(0, wdm); + } + return wdm; + } + } + + } + + private static class WritingDirDoubleSetter extends WritingDirNumberSetter { + + public WritingDirDoubleSetter(String variable) { + super(variable); + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + double value = getDoubleValue(line, startpos); + setValue(getContextObject(stack), new Double(value)); + } + } + + private static class BooleanSetter extends AbstractValueHandler { + private String method; + + public BooleanSetter(String variable) { + this.method = "set" + variable.substring(2); //Cut "Is" in front + } + + protected Object getContextObject(Stack stack) { + return (AFMFile)stack.peek(); + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + Boolean b = getBooleanValue(line, startpos); + //Uses Java Beans API + Statement statement = new Statement(getContextObject(stack), + method, new Object[] {b}); + try { + statement.execute(); + } catch (Exception e) { + //Should never happen + throw new RuntimeException("Bean error: " + e.getMessage()); + } + } + } + + private static class WritingDirBooleanSetter extends BooleanSetter { + + public WritingDirBooleanSetter(String variable) { + super(variable); + } + + protected Object getContextObject(Stack stack) { + if (stack.peek() instanceof AFMWritingDirectionMetrics) { + return (AFMWritingDirectionMetrics)stack.peek(); + } else { + AFMFile afm = (AFMFile)stack.peek(); + AFMWritingDirectionMetrics wdm = afm.getWritingDirectionMetrics(0); + if (wdm == null) { + wdm = new AFMWritingDirectionMetrics(); + afm.setWritingDirectionMetrics(0, wdm); + } + return wdm; + } + } + + } + + private static class FontBBox extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + AFMFile afm = (AFMFile)stack.peek(); + Rectangle rect = new Rectangle(); + int endpos; + + endpos = findValue(line, startpos); + rect.x = Integer.parseInt(line.substring(startpos, endpos)); + startpos = skipToNonWhiteSpace(line, endpos); + + endpos = findValue(line, startpos); + rect.y = Integer.parseInt(line.substring(startpos, endpos)); + startpos = skipToNonWhiteSpace(line, endpos); + + endpos = findValue(line, startpos); + int v = Integer.parseInt(line.substring(startpos, endpos)); + rect.width = v - rect.x; + startpos = skipToNonWhiteSpace(line, endpos); + + endpos = findValue(line, startpos); + v = Integer.parseInt(line.substring(startpos, endpos)); + rect.height = v - rect.y; + startpos = skipToNonWhiteSpace(line, endpos); + + afm.setFontBBox(rect); + } + } + + private static class IsBaseFont extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + if (getBooleanValue(line, startpos).booleanValue()) { + throw new IOException("Only base fonts are currently supported!"); + } + } + } + + private static class IsCIDFont extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + if (getBooleanValue(line, startpos).booleanValue()) { + throw new IOException("CID fonts are currently not supported!"); + } + } + } + + private static class NotImplementedYet extends AbstractValueHandler { + private String key; + + public NotImplementedYet(String key) { + this.key = key; + } + + public void parse(String line, int startpos, Stack stack) throws IOException { + throw new IOException("Support for '" + key + + "' has not been implemented, yet! Font is not supported."); + } + } + + private static class StartDirection extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + int index = getIntegerValue(line, startpos); + AFMWritingDirectionMetrics wdm = new AFMWritingDirectionMetrics(); + AFMFile afm = (AFMFile)stack.peek(); + afm.setWritingDirectionMetrics(index, wdm); + stack.push(wdm); + } + } + + private static class EndDirection extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + if (!(stack.pop() instanceof AFMWritingDirectionMetrics)) { + throw new IOException("AFM format error: nesting incorrect"); + } + } + } + + private static class KPXHandler extends AbstractValueHandler { + public void parse(String line, int startpos, Stack stack) throws IOException { + AFMFile afm = (AFMFile)stack.peek(); + int endpos; + + endpos = findValue(line, startpos); + String name1 = line.substring(startpos, endpos); + startpos = skipToNonWhiteSpace(line, endpos); + + endpos = findValue(line, startpos); + String name2 = line.substring(startpos, endpos); + startpos = skipToNonWhiteSpace(line, endpos); + + endpos = findValue(line, startpos); + double kx = Double.parseDouble(line.substring(startpos, endpos)); + startpos = skipToNonWhiteSpace(line, endpos); + + afm.addXKerning(name1, name2, kx); + } + } + +} diff --git a/src/java/org/apache/fop/fonts/type1/AFMWritingDirectionMetrics.java b/src/java/org/apache/fop/fonts/type1/AFMWritingDirectionMetrics.java new file mode 100644 index 000000000..5b6a9e43b --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/AFMWritingDirectionMetrics.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +/** + * Represents a writing direction metrics section from an AFM file. + */ +public class AFMWritingDirectionMetrics { + + private Number underlinePosition; + private Number underlineThickness; + private double italicAngle; + private boolean isFixedPitch; + + /** + * Returns the UnderlinePosition value. + * @return the underlinePosition + */ + public Number getUnderlinePosition() { + return underlinePosition; + } + + /** + * Sets the UnderlinePosition value. + * @param underlinePosition the underlinePosition to set + */ + public void setUnderlinePosition(Number underlinePosition) { + this.underlinePosition = underlinePosition; + } + + /** + * Returns the UnderlineThickness value. + * @return the underlineThickness + */ + public Number getUnderlineThickness() { + return underlineThickness; + } + + /** + * Sets the UnderlineThickness value. + * @param underlineThickness the underlineThickness to set + */ + public void setUnderlineThickness(Number underlineThickness) { + this.underlineThickness = underlineThickness; + } + + /** + * Returns the ItalicAngle value. + * @return the italicAngle + */ + public double getItalicAngle() { + return italicAngle; + } + + /** + * Sets the ItalicAngle value. + * @param italicAngle the italicAngle to set + */ + public void setItalicAngle(double italicAngle) { + this.italicAngle = italicAngle; + } + + /** + * Returns the IsFixedPitch value. + * @return the isFixedPitch + */ + public boolean isFixedPitch() { + return isFixedPitch; + } + + /** + * Set the IsFixedPitch value. + * @param value the isFixedPitch to set + */ + public void setFixedPitch(boolean value) { + this.isFixedPitch = value; + } + +} diff --git a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java index 1a1f691b2..f5b04442b 100644 --- a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java +++ b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java @@ -21,8 +21,13 @@ package org.apache.fop.fonts.type1; import java.io.IOException; import java.io.InputStream; +import java.util.Iterator; +import java.util.List; import java.util.Set; +import org.apache.commons.io.IOUtils; + +import org.apache.fop.fonts.CodePointMapping; import org.apache.fop.fonts.FontLoader; import org.apache.fop.fonts.FontResolver; import org.apache.fop.fonts.FontType; @@ -33,27 +38,88 @@ import org.apache.fop.fonts.SingleByteFont; */ public class Type1FontLoader extends FontLoader { - private PFMFile pfm; private SingleByteFont singleFont; /** * Constructs a new Type 1 font loader. * @param fontFileURI the URI to the PFB file of a Type 1 font - * @param in the InputStream reading the PFM file of a Type 1 font * @param resolver the font resolver used to resolve URIs * @throws IOException In case of an I/O error */ - public Type1FontLoader(String fontFileURI, InputStream in, FontResolver resolver) + public Type1FontLoader(String fontFileURI, FontResolver resolver) throws IOException { - super(fontFileURI, in, resolver); + super(fontFileURI, resolver); } - /** - * {@inheritDoc} - */ + private String getPFMURI(String pfbURI) { + String pfbExt = pfbURI.substring(pfbURI.length() - 3, pfbURI.length()); + String pfmExt = pfbExt.substring(0, 2) + + (Character.isUpperCase(pfbExt.charAt(2)) ? "M" : "m"); + return pfbURI.substring(0, pfbURI.length() - 4) + "." + pfmExt; + } + + private static final String[] AFM_EXTENSIONS = new String[] {".AFM", ".afm", ".Afm"}; + + /** {@inheritDoc} */ protected void read() throws IOException { - pfm = new PFMFile(); - pfm.load(in); + AFMFile afm = null; + PFMFile pfm = null; + + InputStream afmIn = null; + for (int i = 0; i < AFM_EXTENSIONS.length; i++) { + try { + String afmUri = this.fontFileURI.substring(0, this.fontFileURI.length() - 4) + + AFM_EXTENSIONS[i]; + afmIn = openFontUri(resolver, afmUri); + if (afmIn != null) { + break; + } + } catch (IOException ioe) { + //Ignore, AFM probably not available under the URI + } + } + if (afmIn != null) { + try { + AFMParser afmParser = new AFMParser(); + afm = afmParser.parse(afmIn); + } finally { + IOUtils.closeQuietly(afmIn); + } + } + + String pfmUri = getPFMURI(this.fontFileURI); + InputStream pfmIn = null; + try { + pfmIn = openFontUri(resolver, pfmUri); + } catch (IOException ioe) { + //Ignore, PFM probably not available under the URI + } + if (pfmIn != null) { + try { + pfm = new PFMFile(); + pfm.load(pfmIn); + } finally { + IOUtils.closeQuietly(pfmIn); + } + } + + if (afm == null && pfm == null) { + throw new java.io.FileNotFoundException( + "Neither an AFM nor a PFM file was found for " + this.fontFileURI); + } + if (pfm == null) { + //Cannot do without for now + throw new java.io.FileNotFoundException( + "No PFM file was found for " + this.fontFileURI); + } + buildFont(afm, pfm); + this.loaded = true; + } + + private void buildFont(AFMFile afm, PFMFile pfm) { + if (afm == null && pfm == null) { + throw new IllegalArgumentException("Need at least an AFM or a PFM!"); + } singleFont = new SingleByteFont(); singleFont.setFontType(FontType.TYPE1); if (pfm.getCharSet() >= 0 && pfm.getCharSet() <= 2) { @@ -65,28 +131,127 @@ public class Type1FontLoader extends FontLoader { } singleFont.setResolver(this.resolver); returnFont = singleFont; - returnFont.setFontName(pfm.getPostscriptName()); - String fullName = pfm.getPostscriptName(); - fullName = fullName.replace('-', ' '); //Hack! Try to emulate full name - returnFont.setFullName(fullName); //should be afm.getFullName()!! - //TODO not accurate: we need FullName from the AFM file but we don't have an AFM parser - Set names = new java.util.HashSet(); - names.add(pfm.getWindowsName()); //should be afm.getFamilyName()!! - returnFont.setFamilyNames(names); - returnFont.setCapHeight(pfm.getCapHeight()); - returnFont.setXHeight(pfm.getXHeight()); - returnFont.setAscender(pfm.getLowerCaseAscent()); - returnFont.setDescender(pfm.getLowerCaseDescent()); - returnFont.setFontBBox(pfm.getFontBBox()); + + //Font name + if (afm != null) { + returnFont.setFontName(afm.getFontName()); //PostScript font name + returnFont.setFullName(afm.getFullName()); + Set names = new java.util.HashSet(); + names.add(afm.getFamilyName()); + returnFont.setFamilyNames(names); + } else { + returnFont.setFontName(pfm.getPostscriptName()); + String fullName = pfm.getPostscriptName(); + fullName = fullName.replace('-', ' '); //Hack! Try to emulate full name + returnFont.setFullName(fullName); //emulate afm.getFullName() + Set names = new java.util.HashSet(); + names.add(pfm.getWindowsName()); //emulate afm.getFamilyName() + returnFont.setFamilyNames(names); + } + + //Encoding + if (afm != null) { + String encoding = afm.getEncodingScheme(); + if ("AdobeStandardEncoding".equals(encoding)) { + //Use WinAnsi in this case as it better fits the usual character set people need + singleFont.setEncoding(CodePointMapping.WIN_ANSI_ENCODING); + } else { + String effEncodingName; + if ("FontSpecific".equals(encoding)) { + effEncodingName = afm.getFontName() + "Encoding"; + } else { + effEncodingName = encoding; + } + if (log.isDebugEnabled()) { + log.debug("Unusual font encoding encountered: " + + encoding + " -> " + effEncodingName); + } + CodePointMapping mapping = buildCustomEncoding(effEncodingName, afm); + singleFont.setEncoding(mapping); + } + } + + //Basic metrics + if (afm != null) { + if (afm.getCapHeight() != null) { + returnFont.setCapHeight(afm.getCapHeight().intValue()); + } + if (afm.getXHeight() != null) { + returnFont.setXHeight(afm.getXHeight().intValue()); + } + if (afm.getAscender() != null) { + returnFont.setAscender(afm.getAscender().intValue()); + } + if (afm.getDescender() != null) { + returnFont.setDescender(afm.getDescender().intValue()); + } + returnFont.setFontBBox(afm.getFontBBoxAsIntArray()); + if (afm.getStdVW() != null) { + returnFont.setStemV(afm.getStdVW().intValue()); + } else { + returnFont.setStemV(80); //Arbitrary value + } + returnFont.setItalicAngle((int)afm.getWritingDirectionMetrics(0).getItalicAngle()); + } else { + returnFont.setFontBBox(pfm.getFontBBox()); + returnFont.setStemV(pfm.getStemV()); + returnFont.setItalicAngle(pfm.getItalicAngle()); + } + if (pfm != null) { + if (returnFont.getCapHeight() == 0) { + returnFont.setCapHeight(pfm.getCapHeight()); + } + if (returnFont.getXHeight(1) == 0) { + returnFont.setXHeight(pfm.getXHeight()); + } + if (returnFont.getAscender() == 0) { + returnFont.setAscender(pfm.getLowerCaseAscent()); + } + if (returnFont.getDescender() == 0) { + returnFont.setDescender(pfm.getLowerCaseDescent()); + } + } returnFont.setFirstChar(pfm.getFirstChar()); returnFont.setLastChar(pfm.getLastChar()); returnFont.setFlags(pfm.getFlags()); - returnFont.setStemV(pfm.getStemV()); - returnFont.setItalicAngle(pfm.getItalicAngle()); returnFont.setMissingWidth(0); for (short i = pfm.getFirstChar(); i <= pfm.getLastChar(); i++) { singleFont.setWidth(i, pfm.getCharWidth(i)); } + returnFont.replaceKerningMap(pfm.getKerning()); singleFont.setEmbedFileName(this.fontFileURI); } + + private CodePointMapping buildCustomEncoding(String encodingName, AFMFile afm) { + List chars = afm.getCharMetrics(); + int mappingCount = 0; + //Just count the first time... + Iterator iter = chars.iterator(); + while (iter.hasNext()) { + AFMCharMetrics charMetrics = (AFMCharMetrics)iter.next(); + if (charMetrics.getCharCode() >= 0) { + String u = charMetrics.getUnicodeChars(); + if (u != null) { + mappingCount += u.length(); + } + } + } + //...and now build the table. + int[] table = new int[mappingCount * 2]; + iter = chars.iterator(); + int idx = 0; + while (iter.hasNext()) { + AFMCharMetrics charMetrics = (AFMCharMetrics)iter.next(); + if (charMetrics.getCharCode() >= 0) { + String unicodes = charMetrics.getUnicodeChars(); + for (int i = 0, c = unicodes.length(); i < c; i++) { + table[idx] = charMetrics.getCharCode(); + idx++; + table[idx] = unicodes.charAt(i); + idx++; + } + } + } + return new CodePointMapping(encodingName, table); + } } diff --git a/src/java/org/apache/fop/pdf/CMapBuilder.java b/src/java/org/apache/fop/pdf/CMapBuilder.java new file mode 100644 index 000000000..f55c34fcf --- /dev/null +++ b/src/java/org/apache/fop/pdf/CMapBuilder.java @@ -0,0 +1,157 @@ +/* + * 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.pdf; + +import java.io.IOException; +import java.io.Writer; + +public class CMapBuilder { + + protected String name; + protected Writer writer; + + public CMapBuilder(Writer writer, String name) { + this.writer = writer; + this.name = name; + } + + /** + * Writes the CMap to a Writer. + * @throws IOException if an I/O error occurs + */ + public void writeCMap() throws IOException { + writePreStream(); + writeStreamComments(); + writeCIDInit(); + writeCIDSystemInfo(); + writeVersion("1"); + writeType("1"); + writeName(name); + writeCodeSpaceRange(); + writeCIDRange(); + writeBFEntries(); + writeWrapUp(); + writeStreamAfterComments(); + writeUseCMap(); + } + + protected void writePreStream() throws IOException { + // writer.write("/Type /CMap\n"); + // writer.write(sysInfo.toPDFString()); + // writer.write("/CMapName /" + name + EOL); + } + + protected void writeStreamComments() throws IOException { + writer.write("%!PS-Adobe-3.0 Resource-CMap\n"); + writer.write("%%DocumentNeededResources: ProcSet (CIDInit)\n"); + writer.write("%%IncludeResource: ProcSet (CIDInit)\n"); + writer.write("%%BeginResource: CMap (" + name + ")\n"); + writer.write("%%EndComments\n"); + } + + protected void writeCIDInit() throws IOException { + writer.write("/CIDInit /ProcSet findresource begin\n"); + writer.write("12 dict begin\n"); + writer.write("begincmap\n"); + } + + protected void writeCIDSystemInfo(String registry, String ordering, int supplement) + throws IOException { + writer.write("/CIDSystemInfo 3 dict dup begin\n"); + writer.write(" /Registry ("); + writer.write(registry); + writer.write(") def\n"); + writer.write(" /Ordering ("); + writer.write(ordering); + writer.write(") def\n"); + writer.write(" /Supplement "); + writer.write(Integer.toString(supplement)); + writer.write(" def\n"); + writer.write("end def\n"); + } + + protected void writeCIDSystemInfo() throws IOException { + writeCIDSystemInfo("Adobe", "Identity", 0); + } + + protected void writeVersion(String version) throws IOException { + writer.write("/CMapVersion "); + writer.write(version); + writer.write(" def\n"); + } + + protected void writeType(String type) throws IOException { + writer.write("/CMapType "); + writer.write(type); + writer.write(" def\n"); + } + + protected void writeName(String name) throws IOException { + writer.write("/CMapName /"); + writer.write(name); + writer.write(" def\n"); + } + + protected void writeCodeSpaceRange() throws IOException { + writer.write("1 begincodespacerange\n"); + writer.write("<0000> \n"); + writer.write("endcodespacerange\n"); + } + + protected void writeCIDRange() throws IOException { + writer.write("1 begincidrange\n"); + writer.write("<0000> 0\n"); + writer.write("endcidrange\n"); + } + + protected void writeBFEntries() throws IOException { + // writer.write("1 beginbfrange\n"); + // writer.write("<0020> <0100> <0000>\n"); + // writer.write("endbfrange\n"); + } + + protected void writeWrapUp() throws IOException { + writer.write("endcmap\n"); + writer.write("CMapName currentdict /CMap defineresource pop\n"); + writer.write("end\n"); + writer.write("end\n"); + } + + protected void writeStreamAfterComments() throws IOException { + writer.write("%%EndResource\n"); + writer.write("%%EOF\n"); + } + + protected void writeUseCMap() { + /* + * writer.write(" /Type /CMap"); + * writer.write("/CMapName /" + name + EOL); + * writer.write("/WMode " + wMode + EOL); + * if (base != null) { + * writer.write("/UseCMap "); + * if (base instanceof String) { + * writer.write("/"+base); + * } else {// base instanceof PDFStream + * writer.write(((PDFStream)base).referencePDF()); + * } + * } + */ + } +} \ No newline at end of file diff --git a/src/java/org/apache/fop/pdf/PDFArray.java b/src/java/org/apache/fop/pdf/PDFArray.java index 466ad7d6a..1fe55384c 100644 --- a/src/java/org/apache/fop/pdf/PDFArray.java +++ b/src/java/org/apache/fop/pdf/PDFArray.java @@ -147,7 +147,10 @@ public class PDFArray extends PDFObject { */ public void add(Object obj) { if (obj instanceof PDFObject) { - ((PDFObject)obj).setParent(this); + PDFObject pdfObj = (PDFObject)obj; + if (!pdfObj.hasObjectNumber()) { + pdfObj.setParent(this); + } } this.values.add(obj); } diff --git a/src/java/org/apache/fop/pdf/PDFCIDFontDescriptor.java b/src/java/org/apache/fop/pdf/PDFCIDFontDescriptor.java index 101709613..df552d96e 100644 --- a/src/java/org/apache/fop/pdf/PDFCIDFontDescriptor.java +++ b/src/java/org/apache/fop/pdf/PDFCIDFontDescriptor.java @@ -22,24 +22,14 @@ package org.apache.fop.pdf; // based on work by Takayuki Takeuchi /** - * class representing a font descriptor for CID fonts. + * Class representing a font descriptor for CID fonts. * * Font descriptors for CID fonts are specified on page 227 and onwards of the PDF 1.3 spec. */ public class PDFCIDFontDescriptor extends PDFFontDescriptor { /** - * The language for the font - */ - protected String lang; - - /** - * The cid set stream - */ - protected PDFStream cidSet; - - /** - * create the /FontDescriptor object + * Create a /FontDescriptor object. * * @param basefont the base font name * @param fontBBox the bounding box for the described font @@ -56,31 +46,19 @@ public class PDFCIDFontDescriptor extends PDFFontDescriptor { super(basefont, fontBBox[3], fontBBox[1], capHeight, flags, new PDFRectangle(fontBBox), italicAngle, stemV); - this.lang = lang; + put("MissingWidth", new Integer(500)); + if (lang != null) { + put("Lang", lang); + } } /** * Set the CID set stream. - * @param cidSet the pdf stream cotnaining the CID set + * @param cidSet the PDF stream containing the CID set */ public void setCIDSet(PDFStream cidSet) { - this.cidSet = cidSet; - } - - /** - * Fill in the pdf data for this font descriptor. - * The charset specific dictionary entries are output. - * @param p the string buffer to append the data - */ - protected void fillInPDF(StringBuffer p) { - p.append("\n/MissingWidth 500\n"); - if (lang != null) { - p.append("\n/Lang /"); - p.append(lang); - } if (cidSet != null) { - p.append("\n/CIDSet /"); - p.append(this.cidSet.referencePDF()); + put("CIDSet", cidSet); } } diff --git a/src/java/org/apache/fop/pdf/PDFCMap.java b/src/java/org/apache/fop/pdf/PDFCMap.java index d62e45d85..bdf8108c2 100644 --- a/src/java/org/apache/fop/pdf/PDFCMap.java +++ b/src/java/org/apache/fop/pdf/PDFCMap.java @@ -21,6 +21,8 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.io.StringWriter; +import java.io.Writer; /** * Class representing the CMap encodings. @@ -394,15 +396,6 @@ public class PDFCMap extends PDFStream { this.wMode = mode; } - /** - * Add the contents of this pdf object to the PDF stream. - */ - public void addContents() { - StringBuffer p = new StringBuffer(); - fillInPDF(p); - add(p.toString()); - } - /** * set the base CMap * @@ -422,107 +415,20 @@ public class PDFCMap extends PDFStream { } /** - * Fill in the pdf string for this CMap. - * - * @param p the string buffer to add the pdf data to - */ - public void fillInPDF(StringBuffer p) { - writePreStream(p); - writeStreamComments(p); - writeCIDInit(p); - writeCIDSystemInfo(p); - writeVersionTypeName(p); - writeCodeSpaceRange(p); - writeCIDRange(p); - writeBFEntries(p); - writeWrapUp(p); - writeStreamAfterComments(p); - writeUseCMap(p); - add(p.toString()); - } - - protected void writePreStream(StringBuffer p) { - // p.append("/Type /CMap\n"); - // p.append(sysInfo.toPDFString()); - // p.append("/CMapName /" + name + EOL); - } - - protected void writeStreamComments(StringBuffer p) { - p.append("%!PS-Adobe-3.0 Resource-CMap\n"); - p.append("%%DocumentNeededResources: ProcSet (CIDInit)\n"); - p.append("%%IncludeResource: ProcSet (CIDInit)\n"); - p.append("%%BeginResource: CMap (" + name + ")\n"); - p.append("%%EndComments\n"); - } - - protected void writeCIDInit(StringBuffer p) { - p.append("/CIDInit /ProcSet findresource begin\n"); - p.append("12 dict begin\n"); - p.append("begincmap\n"); - } - - protected void writeCIDSystemInfo(StringBuffer p) { - p.append("/CIDSystemInfo 3 dict dup begin\n"); - p.append(" /Registry (Adobe) def\n"); - p.append(" /Ordering (Identity) def\n"); - p.append(" /Supplement 0 def\n"); - p.append("end def\n"); - } - - protected void writeVersionTypeName(StringBuffer p) { - p.append("/CMapVersion 1 def\n"); - p.append("/CMapType 1 def\n"); - p.append("/CMapName /" + name + " def\n"); - } - - protected void writeCodeSpaceRange(StringBuffer p) { - p.append("1 begincodespacerange\n"); - p.append("<0000> \n"); - p.append("endcodespacerange\n"); - } - - protected void writeCIDRange(StringBuffer p) { - p.append("1 begincidrange\n"); - p.append("<0000> 0\n"); - p.append("endcidrange\n"); - } - - protected void writeBFEntries(StringBuffer p) { - // p.append("1 beginbfrange\n"); - // p.append("<0020> <0100> <0000>\n"); - // p.append("endbfrange\n"); - } - - protected void writeWrapUp(StringBuffer p) { - p.append("endcmap\n"); - p.append("CMapName currentdict /CMap defineresource pop\n"); - p.append("end\n"); - p.append("end\n"); - } - - protected void writeStreamAfterComments(StringBuffer p) { - p.append("%%EndResource\n"); - p.append("%%EOF\n"); - } - - protected void writeUseCMap(StringBuffer p) { - /* - * p.append(" /Type /CMap"); - * p.append("/CMapName /" + name + EOL); - * p.append("/WMode " + wMode + EOL); - * if (base != null) { - * p.append("/UseCMap "); - * if (base instanceof String) { - * p.append("/"+base); - * } else {// base instanceof PDFStream - * p.append(((PDFStream)base).referencePDF()); - * } - * } - */ + * Creates the CMapBuilder that will build the CMap's content. + * @param writer a Writer to write the CMap's contents to + * @return the newly created CMapBuilder + */ + protected CMapBuilder createCMapBuilder(Writer writer) { + return new CMapBuilder(writer, this.name); } - + + /** {@inheritDoc} */ protected int output(OutputStream stream) throws IOException { - fillInPDF(new StringBuffer()); + StringWriter writer = new StringWriter(); + CMapBuilder builder = createCMapBuilder(writer); + builder.writeCMap(); + add(writer.getBuffer().toString()); //TODO Could be optimized by not buffering return super.output(stream); } } diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java index 49d48312c..34fe4c389 100644 --- a/src/java/org/apache/fop/pdf/PDFDictionary.java +++ b/src/java/org/apache/fop/pdf/PDFDictionary.java @@ -65,6 +65,12 @@ public class PDFDictionary extends PDFObject { * @param value the value */ public void put(String name, Object value) { + if (value instanceof PDFObject) { + PDFObject pdfObj = (PDFObject)value; + if (!pdfObj.hasObjectNumber()) { + pdfObj.setParent(this); + } + } if (!entries.containsKey(name)) { this.order.add(name); } diff --git a/src/java/org/apache/fop/pdf/PDFEncoding.java b/src/java/org/apache/fop/pdf/PDFEncoding.java index b9064dc1f..b2fba6e53 100644 --- a/src/java/org/apache/fop/pdf/PDFEncoding.java +++ b/src/java/org/apache/fop/pdf/PDFEncoding.java @@ -20,12 +20,11 @@ package org.apache.fop.pdf; // Java -import java.util.List; -import java.util.Map; -import java.util.Iterator; +import java.util.Collections; +import java.util.Set; /** - * class representing an /Encoding object. + * Class representing an /Encoding object. * * A small object expressing the base encoding name and * the differences from the base encoding. @@ -34,90 +33,113 @@ import java.util.Iterator; * * Encodings are specified in section 5.5.5 of the PDF 1.4 spec. */ -public class PDFEncoding extends PDFObject { +public class PDFEncoding extends PDFDictionary { - /** - * the name for the standard encoding scheme - */ + /** the name for the standard encoding scheme */ + public static final String STANDARD_ENCODING = "StandardEncoding"; + /** the name for the Mac Roman encoding scheme */ public static final String MAC_ROMAN_ENCODING = "MacRomanEncoding"; - - /** - * the name for the standard encoding scheme - */ + /** the name for the Mac Export encoding scheme */ public static final String MAC_EXPERT_ENCODING = "MacExpertEncoding"; - - /** - * the name for the standard encoding scheme - */ + /** the name for the WinAnsi encoding scheme */ public static final String WIN_ANSI_ENCODING = "WinAnsiEncoding"; + /** the name for the PDF document encoding scheme */ + public static final String PDF_DOC_ENCODING = "PDFDocEncoding"; - /** - * the name for the base encoding. - * One of the three base encoding scheme names or - * the default font's base encoding if null. - */ - protected String basename; - - /** - * the differences from the base encoding - */ - protected Map differences; + /** the set of predefined encodings that can be assumed present in a PDF viewer */ + private static final Set PREDEFINED_ENCODINGS; + + static { + Set encodings = new java.util.HashSet(); + encodings.add(STANDARD_ENCODING); + encodings.add(MAC_ROMAN_ENCODING); + encodings.add(MAC_EXPERT_ENCODING); + encodings.add(WIN_ANSI_ENCODING); + encodings.add(PDF_DOC_ENCODING); + PREDEFINED_ENCODINGS = Collections.unmodifiableSet(encodings); + } /** - * create the /Encoding object + * Create a new /Encoding object. * * @param basename the name of the character encoding schema */ public PDFEncoding(String basename) { - - /* generic creation of PDF object */ super(); - /* set fields using paramaters */ - this.basename = basename; - this.differences = new java.util.HashMap(); + put("Type", new PDFName("Encoding")); + if (basename != null) { + put("BaseEncoding", new PDFName(basename)); + } } /** - * add differences to the encoding - * - * @param code the first index of the sequence to be changed - * @param sequence the sequence of glyph names (as String) + * Indicates whether a given encoding is one of the predefined encodings. + * @param name the encoding name (ex. "StandardEncoding") + * @return true if it is a predefined encoding + */ + public static boolean isPredefinedEncoding(String name) { + return PREDEFINED_ENCODINGS.contains(name); + } + + /** + * Creates and returns a new DifferencesBuilder instance for constructing the Differences + * array. + * @return the DifferencesBuilder */ - public void addDifferences(int code, List sequence) { - differences.put(new Integer(code), sequence); + public DifferencesBuilder createDifferencesBuilder() { + return new DifferencesBuilder(); } /** - * {@inheritDoc} + * Sets the Differences value. + * @param differences the differences. + */ + public void setDifferences(PDFArray differences) { + put("Differences", differences); + } + + /** + * Builder class for constructing the Differences array. */ - public String toPDFString() { - StringBuffer p = new StringBuffer(128); - p.append(getObjectID() - + "<< /Type /Encoding"); - if ((basename != null) && (!basename.equals(""))) { - p.append("\n/BaseEncoding /" + this.basename); + public class DifferencesBuilder { + + private PDFArray differences = new PDFArray(); + private int currentCode = -1; + + /** + * Start a new difference. + * @param code the starting code index inside the encoding + * @return this builder instance + */ + public DifferencesBuilder addDifference(int code) { + this.currentCode = code; + this.differences.add(new Integer(code)); + return this; } - if (!differences.isEmpty()) { - p.append("\n/Differences [ "); - Object code; - Iterator codes = differences.keySet().iterator(); - while (codes.hasNext()) { - code = codes.next(); - p.append(" "); - p.append(code); - List sequence = (List)differences.get(code); - for (int i = 0; i < sequence.size(); i++) { - p.append(" /"); - p.append((String)sequence.get(i)); - } + + /** + * Adds a character name to the current difference. + * @param name the character name + * @return this builder instance + */ + public DifferencesBuilder addName(String name) { + if (this.currentCode < 0) { + throw new IllegalStateException("addDifference(int) must be called first"); } - p.append(" ]"); + this.differences.add(new PDFName(name)); + return this; + } + + /** + * Creates and returns the PDFArray representing the Differences entry. + * @return the Differences entry + */ + public PDFArray toPDFArray() { + return this.differences; } - p.append(" >>\nendobj\n"); - return p.toString(); } - + /* * example (p. 214) * 25 0 obj diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index d0c48ee39..a50ef2545 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -40,15 +40,18 @@ import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.fonts.Glyphs; import org.apache.xmlgraphics.xmp.Metadata; import org.apache.fop.fonts.CIDFont; +import org.apache.fop.fonts.CodePointMapping; import org.apache.fop.fonts.CustomFont; import org.apache.fop.fonts.FontDescriptor; import org.apache.fop.fonts.FontMetrics; import org.apache.fop.fonts.FontType; import org.apache.fop.fonts.LazyFont; import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.SingleByteFont; import org.apache.fop.fonts.Typeface; import org.apache.fop.fonts.truetype.FontFileReader; import org.apache.fop.fonts.truetype.TTFSubSetFile; @@ -1182,6 +1185,7 @@ public class PDFFactory { } if (descriptor == null) { + //Usually Base 14 fonts PDFFont font = new PDFFont(fontname, FontType.TYPE1, basefont, encoding); getDocument().registerObject(font); return font; @@ -1190,30 +1194,11 @@ public class PDFFactory { PDFFontDescriptor pdfdesc = makeFontDescriptor(descriptor); - PDFFontNonBase14 font = null; - if (fonttype == FontType.TYPE0) { - /* - * Temporary commented out - customized CMaps - * isn't needed until /ToUnicode support is added - * PDFCMap cmap = new PDFCMap(++this.objectcount, - * "fop-ucs-H", - * new PDFCIDSystemInfo("Adobe", - * "Identity", - * 0)); - * cmap.addContents(); - * this.objects.add(cmap); - */ - font = (PDFFontNonBase14)PDFFont.createFont(fontname, fonttype, - basefont, "Identity-H"); - } else { - - font = (PDFFontNonBase14)PDFFont.createFont(fontname, fonttype, - basefont, encoding); - } + PDFFont font = null; + font = (PDFFont)PDFFont.createFont(fontname, fonttype, + basefont, encoding); getDocument().registerObject(font); - font.setDescriptor(pdfdesc); - if (fonttype == FontType.TYPE0) { CIDFont cidMetrics; if (metrics instanceof LazyFont) { @@ -1233,7 +1218,7 @@ public class PDFFactory { (PDFCIDFontDescriptor)pdfdesc); getDocument().registerObject(cidFont); - PDFCMap cmap = new PDFToUnicodeCMap(cidMetrics, "fop-ucs-H", + PDFCMap cmap = new PDFToUnicodeCMap(cidMetrics.getCharsUsed(), "fop-ucs-H", new PDFCIDSystemInfo("Adobe", "Identity", 0)); @@ -1241,16 +1226,57 @@ public class PDFFactory { ((PDFFontType0)font).setCMAP(cmap); ((PDFFontType0)font).setDescendantFonts(cidFont); } else { - int firstChar = 0; - int lastChar = 255; - if (metrics instanceof CustomFont) { - CustomFont cf = (CustomFont)metrics; - firstChar = cf.getFirstChar(); - lastChar = cf.getLastChar(); + PDFFontNonBase14 nonBase14 = (PDFFontNonBase14)font; + nonBase14.setDescriptor(pdfdesc); + + SingleByteFont singleByteFont; + if (metrics instanceof LazyFont) { + singleByteFont = (SingleByteFont)((LazyFont)metrics).getRealFont(); + } else { + singleByteFont = (SingleByteFont)metrics; } - font.setWidthMetrics(firstChar, + int firstChar = singleByteFont.getFirstChar(); + int lastChar = singleByteFont.getLastChar(); + nonBase14.setWidthMetrics(firstChar, lastChar, makeArray(metrics.getWidths())); + + //Handle encoding + CodePointMapping mapping = singleByteFont.getCodePointMapping(); + if (PDFEncoding.isPredefinedEncoding(mapping.getName())) { + font.setEncoding(mapping.getName()); + } else { + CodePointMapping winansi = CodePointMapping.getMapping( + CodePointMapping.WIN_ANSI_ENCODING); + PDFEncoding pdfEncoding = new PDFEncoding(winansi.getName()); + PDFEncoding.DifferencesBuilder builder + = pdfEncoding.createDifferencesBuilder(); + int start = -1; + for (int i = 0; i < 256; i++) { + char wac = winansi.getUnicodeForIndex(i); + char c = mapping.getUnicodeForIndex(i); + if (wac != c) { + if (start != i) { + builder.addDifference(i); + start = i; + } + builder.addName(Glyphs.charToGlyphName(c)); + start++; + } + } + pdfEncoding.setDifferences(builder.toPDFArray()); + font.setEncoding(pdfEncoding); + + /* JM: What I thought would be a necessity with custom encodings turned out to + * be a bug in Adobe Acrobat 8. The following section just demonstrates how + * to generate a ToUnicode CMap for a Type 1 font. + PDFCMap cmap = new PDFToUnicodeCMap(mapping.getUnicodeCharMap(), + "fop-ucs-H", + new PDFCIDSystemInfo("Adobe", "Identity", 0)); + getDocument().registerObject(cmap); + nonBase14.setToUnicode(cmap); + */ + } } return font; diff --git a/src/java/org/apache/fop/pdf/PDFFont.java b/src/java/org/apache/fop/pdf/PDFFont.java index 0730376db..497565e9c 100644 --- a/src/java/org/apache/fop/pdf/PDFFont.java +++ b/src/java/org/apache/fop/pdf/PDFFont.java @@ -30,38 +30,11 @@ import org.apache.fop.fonts.FontType; *

* Fonts are specified on page 198 and onwards of the PDF 1.3 spec. */ -public class PDFFont extends PDFObject { - - /** - * the internal name for the font (eg "F1") - */ - protected String fontname; - - /** - * the font's subtype - * (as defined by the constants FontType: TYPE0, TYPE1, MMTYPE1, TYPE3, TRUETYPE) - */ - protected FontType subtype; - - /** - * the base font name (eg "Helvetica") - */ - protected String basefont; - - /** - * the character encoding scheme used by the font. - * It can be a String for standard encodings, or - * a PDFEncoding for a more complex scheme, or - * a PDFStream containing a CMap in a Type0 font. - * If null then not written out in the PDF. - */ - protected Object encoding; - - /** - * the Unicode mapping mechanism - */ - // protected PDFToUnicode mapping; +public class PDFFont extends PDFDictionary { + /** Internal F-number for each font (it is not written to the font dict) */ + private String fontname; + /** * create the /Font object * @@ -72,19 +45,45 @@ public class PDFFont extends PDFObject { */ public PDFFont(String fontname, FontType subtype, String basefont, - Object encoding /* , PDFToUnicode mapping */) { + Object encoding) { /* generic creation of PDF object */ super(); - /* set fields using paramaters */ this.fontname = fontname; - this.subtype = subtype; - this.basefont = basefont; - this.encoding = encoding; - // this.mapping = mapping; + put("Type", new PDFName("Font")); + put("Subtype", getPDFNameForFontType(subtype)); + //put("Name", new PDFName(fontname)); + put("BaseFont", new PDFName(basefont)); + if (encoding instanceof PDFEncoding) { + setEncoding((PDFEncoding)encoding); + } else if (encoding instanceof String) { + setEncoding((String)encoding); + } else { + throw new IllegalArgumentException("Illegal value for encoding"); + } } + /** + * Sets the Encoding value of the font. + * @param encoding the encoding + */ + public void setEncoding(String encoding) { + if (encoding != null) { + put("Encoding", new PDFName(encoding)); + } + } + + /** + * Sets the Encoding value of the font. + * @param encoding the encoding + */ + public void setEncoding(PDFEncoding encoding) { + if (encoding != null) { + put("Encoding", encoding); + } + } + /** * factory method with the basic parameters * @@ -111,80 +110,42 @@ public class PDFFont extends PDFObject { return new PDFFontTrueType(fontname, basefont, encoding); } else { - return null; // should not happend - } - } - - /** - * factory method with the extended parameters - * for Type1, MMType1 and TrueType - * - * @param fontname the internal name for the font - * @param subtype the font's subtype - * @param basefont the base font name - * @param encoding the character encoding schema used by the font - * @param firstChar the first character code in the font - * @param lastChar the last character code in the font - * @param widths an array of size (lastChar - firstChar +1) - * @param descriptor the descriptor for other font's metrics - * @return the generated PDFFont object - */ - public static PDFFont createFont(String fontname, - FontType subtype, String basefont, - Object encoding, int firstChar, - int lastChar, PDFArray widths, - PDFFontDescriptor descriptor) { - - PDFFontNonBase14 font; - if (subtype == FontType.TYPE0) { - font = new PDFFontType0(fontname, basefont, - encoding); - font.setDescriptor(descriptor); - return font; - } else if ((subtype == FontType.TYPE1) - || (subtype == FontType.MMTYPE1)) { - font = new PDFFontType1(fontname, basefont, - encoding); - font.setWidthMetrics(firstChar, lastChar, widths); - font.setDescriptor(descriptor); - return font; - } else if (subtype == FontType.TYPE3) { - return null; //NYI, should not happend - } else if (subtype == FontType.TRUETYPE) { - font = new PDFFontTrueType(fontname, basefont, - encoding); - font.setWidthMetrics(firstChar, lastChar, widths); - font.setDescriptor(descriptor); - return font; - } else { - return null; // should not happend + return null; // should not happen } } /** - * get the internal name used for this font + * Get the internal name used for this font. * @return the internal name */ public String getName() { return this.fontname; } + + /** + * Returns the name of the BaseFont. + * @return the BaseFont + */ + public PDFName getBaseFont() { + return (PDFName)get("BaseFont"); + } /** * Returns the PDF name for a certain font type. * @param fontType font type * @return String corresponding PDF name */ - protected String getPDFNameForFontType(FontType fontType) { + protected PDFName getPDFNameForFontType(FontType fontType) { if (fontType == FontType.TYPE0) { - return fontType.getName(); + return new PDFName(fontType.getName()); } else if (fontType == FontType.TYPE1) { - return fontType.getName(); + return new PDFName(fontType.getName()); } else if (fontType == FontType.MMTYPE1) { - return fontType.getName(); + return new PDFName(fontType.getName()); } else if (fontType == FontType.TYPE3) { - return fontType.getName(); + return new PDFName(fontType.getName()); } else if (fontType == FontType.TRUETYPE) { - return fontType.getName(); + return new PDFName(fontType.getName()); } else { throw new IllegalArgumentException("Unsupported font type: " + fontType.getName()); } @@ -198,46 +159,9 @@ public class PDFFont extends PDFObject { if (this.getClass() == PDFFont.class) { throw new PDFConformanceException("For " + getDocumentSafely().getProfile() + ", all fonts, even the base 14" - + " fonts, have to be embedded! Offending font: " + this.basefont); + + " fonts, have to be embedded! Offending font: " + getBaseFont()); } } } - /** - * {@inheritDoc} - */ - public String toPDFString() { - validate(); - StringBuffer p = new StringBuffer(128); - p.append(getObjectID()); - p.append("<< /Type /Font\n/Subtype /" - + getPDFNameForFontType(this.subtype) - + "\n/Name /" + this.fontname - + "\n/BaseFont /" + this.basefont); - if (encoding != null) { - p.append("\n/Encoding "); - if (encoding instanceof PDFEncoding) { - p.append(((PDFEncoding)this.encoding).referencePDF()); - } else if (encoding instanceof PDFStream) { - p.append(((PDFStream)this.encoding).referencePDF()); - } else { - p.append("/").append((String)encoding); - } - } - fillInPDF(p); - p.append(" >>\nendobj\n"); - return p.toString(); - } - - /** - * This method is called to receive the specifics for the font's subtype. - *

- * The given buffer already contains the fields common to all font types. - * - * @param target the buffer to be completed with the type specific fields - */ - protected void fillInPDF(StringBuffer target) { - //nop - } - } diff --git a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java index be460dcc2..aa40bc35b 100644 --- a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java +++ b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java @@ -26,29 +26,7 @@ import org.apache.fop.fonts.FontType; *

* Font descriptors are specified on page 222 and onwards of the PDF 1.3 spec. */ -public class PDFFontDescriptor extends PDFObject { - - // Required fields - private int ascent; - private int capHeight; - private int descent; - private int flags; - private PDFRectangle fontBBox; - private String basefont; // PDF-spec: FontName - private int italicAngle; - private int stemV; - // Optional fields - private int stemH = 0; - private int xHeight = 0; - private int leading = 0; - private int avgWidth = 0; - private int maxWidth = 0; - private int missingWidth = 0; - private AbstractPDFStream fontfile; - private AbstractPDFStream cidSet; - // private String charSet = null; - - private FontType subtype; +public class PDFFontDescriptor extends PDFDictionary { /** * Create the /FontDescriptor object @@ -66,19 +44,17 @@ public class PDFFontDescriptor extends PDFObject { int descent, int capHeight, int flags, PDFRectangle fontBBox, int italicAngle, int stemV) { - - /* generic creation of PDF object */ super(); - /* set fields using paramaters */ - this.basefont = basefont; - this.ascent = ascent; - this.descent = descent; - this.capHeight = capHeight; - this.flags = flags; - this.fontBBox = fontBBox; - this.italicAngle = italicAngle; - this.stemV = stemV; + put("Type", new PDFName("FontDescriptor")); + put("FontName", new PDFName(basefont)); + put("FontBBox", fontBBox); + put("Flags", flags); + put("CapHeight", capHeight); + put("Ascent", ascent); + put("Descent", descent); + put("ItalicAngle", italicAngle); + put("StemV", stemV); } /** @@ -97,12 +73,24 @@ public class PDFFontDescriptor extends PDFObject { */ public void setMetrics(int avgWidth, int maxWidth, int missingWidth, int leading, int stemH, int xHeight) { - this.avgWidth = avgWidth; - this.maxWidth = maxWidth; - this.missingWidth = missingWidth; - this.leading = leading; - this.stemH = stemH; - this.xHeight = xHeight; + if (avgWidth != 0) { + put("AvgWidth", avgWidth); + } + if (maxWidth != 0) { + put("MaxWidth", maxWidth); + } + if (missingWidth != 0) { + put("MissingWidth", missingWidth); + } + if (leading != 0) { + put("Leading", leading); + } + if (stemH != 0) { + put("StemH", stemH); + } + if (xHeight != 0) { + put("XHeight", xHeight); + } } /** @@ -112,13 +100,24 @@ public class PDFFontDescriptor extends PDFObject { * @param fontfile the stream containing an embedded font */ public void setFontFile(FontType subtype, AbstractPDFStream fontfile) { - this.subtype = subtype; - this.fontfile = fontfile; + if (subtype == FontType.TYPE1) { + put("FontFile", fontfile); + } else { + put("FontFile2", fontfile); + } } /** @return the FontFile or null if the font is not embedded */ public AbstractPDFStream getFontFile() { - return this.fontfile; + AbstractPDFStream stream; + stream = (AbstractPDFStream)get("FontFile"); + if (stream == null) { + stream = (AbstractPDFStream)get("FontFile2"); + } + if (stream == null) { + stream = (AbstractPDFStream)get("FontFile3"); + } + return stream; } /** @@ -126,19 +125,18 @@ public class PDFFontDescriptor extends PDFObject { * @param cidSet the CIDSet stream */ public void setCIDSet(AbstractPDFStream cidSet) { - this.cidSet = cidSet; + put("CIDSet", cidSet); } /** @return the CIDSet stream or null if not applicable */ public AbstractPDFStream getCIDSet() { - return this.cidSet; + return (AbstractPDFStream)get("CIDSet"); } - // public void setCharSet(){}//for subset fonts - /** * {@inheritDoc} */ + /* public String toPDFString() { StringBuffer p = new StringBuffer(128); p.append(getObjectID() @@ -201,7 +199,7 @@ public class PDFFontDescriptor extends PDFObject { fillInPDF(p); p.append(" >>\nendobj\n"); return p.toString(); - } + }*/ /** * Fill in the specifics for the font's descriptor. @@ -209,9 +207,9 @@ public class PDFFontDescriptor extends PDFObject { * The given buffer already contains the fields common to all descriptors. * * @param begin the buffer to be completed with the specific fields - */ + *//* protected void fillInPDF(StringBuffer begin) { //nop - } + }*/ } diff --git a/src/java/org/apache/fop/pdf/PDFFontNonBase14.java b/src/java/org/apache/fop/pdf/PDFFontNonBase14.java index 0689cbbdb..b97d5deec 100644 --- a/src/java/org/apache/fop/pdf/PDFFontNonBase14.java +++ b/src/java/org/apache/fop/pdf/PDFFontNonBase14.java @@ -27,26 +27,6 @@ import org.apache.fop.fonts.FontType; */ public abstract class PDFFontNonBase14 extends PDFFont { - /** - * first character code in the font - */ - protected int firstChar; - - /** - * last character code in the font - */ - protected int lastChar; - - /** - * widths of characters from firstChar to lastChar - */ - protected PDFArray widths; - - /** - * descriptor of font metrics - */ - protected PDFFontDescriptor descriptor; - /** * Create the /Font object * @@ -61,8 +41,6 @@ public abstract class PDFFontNonBase14 extends PDFFont { /* generic creation of PDF object */ super(fontname, subtype, basefont, encoding); - - this.descriptor = null; } /** @@ -74,10 +52,9 @@ public abstract class PDFFontNonBase14 extends PDFFont { */ public void setWidthMetrics(int firstChar, int lastChar, PDFArray widths) { - /* set fields using paramaters */ - this.firstChar = firstChar; - this.lastChar = lastChar; - this.widths = widths; + put("FirstChar", new Integer(firstChar)); + put("LastChar", new Integer(lastChar)); + put("Widths", widths); } /** @@ -86,12 +63,20 @@ public abstract class PDFFontNonBase14 extends PDFFont { * @param descriptor the descriptor for other font's metrics */ public void setDescriptor(PDFFontDescriptor descriptor) { - this.descriptor = descriptor; + put("FontDescriptor", descriptor); } /** @return the FontDescriptor or null if there is none */ public PDFFontDescriptor getDescriptor() { - return this.descriptor; + return (PDFFontDescriptor)get("FontDescriptor"); + } + + /** + * Sets a ToUnicode CMap. + * @param cmap the ToUnicode character map + */ + public void setToUnicode(PDFCMap cmap) { + put("ToUnicode", cmap); } /** {@inheritDoc} */ @@ -104,20 +89,4 @@ public abstract class PDFFontNonBase14 extends PDFFont { } } - /** - * {@inheritDoc} - */ - protected void fillInPDF(StringBuffer target) { - target.append("\n/FirstChar "); - target.append(firstChar); - target.append("\n/LastChar "); - target.append(lastChar); - target.append("\n/Widths "); - target.append(this.widths.referencePDF()); - if (descriptor != null) { - target.append("\n/FontDescriptor "); - target.append(this.descriptor.referencePDF()); - } - } - } diff --git a/src/java/org/apache/fop/pdf/PDFFontTrueType.java b/src/java/org/apache/fop/pdf/PDFFontTrueType.java index eb3a94a27..a5636f951 100644 --- a/src/java/org/apache/fop/pdf/PDFFontTrueType.java +++ b/src/java/org/apache/fop/pdf/PDFFontTrueType.java @@ -39,9 +39,7 @@ public class PDFFontTrueType extends PDFFontNonBase14 { public PDFFontTrueType(String fontname, String basefont, Object encoding) { - - /* generic creation of PDF object */ - super(fontname, FontType.TRUETYPE, basefont, encoding /* , mapping */); + super(fontname, FontType.TRUETYPE, basefont, encoding); } } diff --git a/src/java/org/apache/fop/pdf/PDFFontType0.java b/src/java/org/apache/fop/pdf/PDFFontType0.java index e8a338ad4..29d6d1b4f 100644 --- a/src/java/org/apache/fop/pdf/PDFFontType0.java +++ b/src/java/org/apache/fop/pdf/PDFFontType0.java @@ -26,17 +26,7 @@ import org.apache.fop.fonts.FontType; *

* Type0 fonts are specified on page 208 and onwards of the PDF 1.3 spec. */ -public class PDFFontType0 extends PDFFontNonBase14 { - - /** - * This should be an array of CIDFont but only the first one is used - */ - protected PDFCIDFont descendantFonts; - - /** - * The character map - */ - protected PDFCMap cmap; +public class PDFFontType0 extends PDFFont { /** * Create the /Font object @@ -48,13 +38,7 @@ public class PDFFontType0 extends PDFFontNonBase14 { public PDFFontType0(String fontname, String basefont, Object encoding) { - - /* generic creation of PDF object */ - super(fontname, FontType.TYPE0, basefont, encoding /* , mapping */); - - /* set fields using paramaters */ - this.descendantFonts = null; - cmap = null; + super(fontname, FontType.TYPE0, basefont, encoding); } /** @@ -69,12 +53,9 @@ public class PDFFontType0 extends PDFFontNonBase14 { String basefont, Object encoding, PDFCIDFont descendantFonts) { + super(fontname, FontType.TYPE0, basefont, encoding); - /* generic creation of PDF object */ - super(fontname, FontType.TYPE0, basefont, encoding /* , mapping */); - - /* set fields using paramaters */ - this.descendantFonts = descendantFonts; + setDescendantFonts(descendantFonts); } /** @@ -82,7 +63,7 @@ public class PDFFontType0 extends PDFFontNonBase14 { * @param descendantFonts the CIDFont upon which this font is based */ public void setDescendantFonts(PDFCIDFont descendantFonts) { - this.descendantFonts = descendantFonts; + put("DescendantFonts", new PDFArray(this, new PDFObject[] {descendantFonts})); } /** @@ -90,20 +71,7 @@ public class PDFFontType0 extends PDFFontNonBase14 { * @param cmap the character map */ public void setCMAP(PDFCMap cmap) { - this.cmap = cmap; - } - - /** - * {@inheritDoc} - */ - protected void fillInPDF(StringBuffer target) { - if (descendantFonts != null) { - target.append("\n/DescendantFonts [ " - + this.descendantFonts.referencePDF() + " ] "); - } - if (cmap != null) { - target.append("\n/ToUnicode " + cmap.referencePDF()); - } + put("ToUnicode", cmap); } } diff --git a/src/java/org/apache/fop/pdf/PDFFontType3.java b/src/java/org/apache/fop/pdf/PDFFontType3.java index 48df26fdf..96ecb6b5a 100644 --- a/src/java/org/apache/fop/pdf/PDFFontType3.java +++ b/src/java/org/apache/fop/pdf/PDFFontType3.java @@ -31,26 +31,6 @@ import org.apache.fop.fonts.FontType; */ public class PDFFontType3 extends PDFFontNonBase14 { - /** - * font's required /FontBBox bounding box - */ - protected PDFRectangle fontBBox; - - /** - * font's required /FontMatrix array - */ - protected PDFArray fontMatrix; - - /** - * font's required /CharProcs dictionary - */ - protected PDFCharProcs charProcs; - - /** - * font's optional /Resources object - */ - protected PDFResources resources; - /** * Create the /Font object * @@ -61,13 +41,7 @@ public class PDFFontType3 extends PDFFontNonBase14 { public PDFFontType3(String fontname, String basefont, Object encoding) { - - /* generic creation of PDF object */ - super(fontname, FontType.TYPE3, basefont, encoding /* , mapping */); - - this.fontBBox = null; - this.fontMatrix = null; - this.charProcs = null; + super(fontname, FontType.TYPE3, basefont, encoding); } /** @@ -89,9 +63,9 @@ public class PDFFontType3 extends PDFFontNonBase14 { /* generic creation of PDF object */ super(fontname, FontType.TYPE3, basefont, encoding /* , mapping */); - this.fontBBox = fontBBox; - this.fontMatrix = fontMatrix; - this.charProcs = charProcs; + setFontBBox(fontBBox); + setFontMatrix(fontMatrix); + setCharProcs(charProcs); } /** @@ -100,7 +74,7 @@ public class PDFFontType3 extends PDFFontNonBase14 { * @param bbox bounding box for the font */ public void setFontBBox(PDFRectangle bbox) { - this.fontBBox = bbox; + put("FontBBox", bbox); } /** @@ -109,7 +83,7 @@ public class PDFFontType3 extends PDFFontNonBase14 { * @param matrix the transformation matrix for the font */ public void setFontMatrix(PDFArray matrix) { - this.fontMatrix = matrix; + put("FontMatrix", matrix); } /** @@ -120,25 +94,7 @@ public class PDFFontType3 extends PDFFontNonBase14 { * @param chars the glyphs' dictionary */ public void setCharProcs(PDFCharProcs chars) { - this.charProcs = chars; - } - - /** - * {@inheritDoc} - */ - protected void fillInPDF(StringBuffer target) { - if (fontBBox != null) { - target.append("\n/FontBBox "); - target.append(fontBBox.toPDF()); - } - if (fontMatrix != null) { - target.append("\n/FontMatrix "); - target.append(fontMatrix.toPDF()); - } - if (charProcs != null) { - target.append("\n/CharProcs "); - target.append(charProcs.referencePDF()); - } + put("CharProcs", chars); } } diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java index d43ae71f1..8dc4d8794 100644 --- a/src/java/org/apache/fop/pdf/PDFObject.java +++ b/src/java/org/apache/fop/pdf/PDFObject.java @@ -103,7 +103,9 @@ public abstract class PDFObject implements PDFWritable { */ public void setObjectNumber(int objnum) { this.objnum = objnum; + PDFDocument doc = getDocument(); setParent(null); + setDocument(doc); //Restore reference to PDFDocument after setting parent to null if (log.isTraceEnabled()) { log.trace("Assigning " + this + " object number " + objnum); } @@ -141,7 +143,8 @@ public abstract class PDFObject implements PDFWritable { public final PDFDocument getDocumentSafely() { final PDFDocument doc = getDocument(); if (doc == null) { - throw new IllegalStateException("Parent PDFDocument is unavailable"); + throw new IllegalStateException("Parent PDFDocument is unavailable on " + + getClass().getName()); } return doc; } diff --git a/src/java/org/apache/fop/pdf/PDFRectangle.java b/src/java/org/apache/fop/pdf/PDFRectangle.java index e84227adf..ca97c2474 100644 --- a/src/java/org/apache/fop/pdf/PDFRectangle.java +++ b/src/java/org/apache/fop/pdf/PDFRectangle.java @@ -19,12 +19,16 @@ package org.apache.fop.pdf; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + /** * class representing a rectangle * * Rectangles are specified on page 183 of the PDF 1.3 spec. */ -public class PDFRectangle { +public class PDFRectangle implements PDFWritable { /** * lower left x coordinate @@ -73,23 +77,17 @@ public class PDFRectangle { this.ury = array[3]; } - /** - * produce the PDF representation for the object - * - * @return the PDF - */ - public byte[] toPDF() { - return PDFDocument.encode(toPDFString()); + private String format() { + return "[" + llx + " " + lly + " " + urx + " " + ury + "]"; } - /** - * Create a PDF string for this rectangle. - * - * @return the pdf string - */ - public String toPDFString() { - return new String(" [" + llx + " " + lly + " " + urx + " " + ury - + "] "); + /** {@inheritDoc} */ + public String toString() { + return "PDFRectangle" + format(); } + /** {@inheritDoc} */ + public void outputInline(OutputStream out, Writer writer) throws IOException { + writer.write(format()); + } } diff --git a/src/java/org/apache/fop/pdf/PDFT1Stream.java b/src/java/org/apache/fop/pdf/PDFT1Stream.java index 08a626009..d122743b0 100644 --- a/src/java/org/apache/fop/pdf/PDFT1Stream.java +++ b/src/java/org/apache/fop/pdf/PDFT1Stream.java @@ -23,7 +23,6 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; -// FOP import org.apache.fop.fonts.type1.PFBData; /** @@ -54,7 +53,9 @@ public class PDFT1Stream extends AbstractPDFStream { if (pfb == null) { throw new IllegalStateException("pfb must not be null at this point"); } - log.debug("Writing " + pfb.getLength() + " bytes of Type 1 font data"); + if (log.isDebugEnabled()) { + log.debug("Writing " + pfb.getLength() + " bytes of Type 1 font data"); + } int length = super.output(stream); log.debug("Embedded Type1 font"); diff --git a/src/java/org/apache/fop/pdf/PDFTTFStream.java b/src/java/org/apache/fop/pdf/PDFTTFStream.java index 9f4c543ad..b3488f6a1 100644 --- a/src/java/org/apache/fop/pdf/PDFTTFStream.java +++ b/src/java/org/apache/fop/pdf/PDFTTFStream.java @@ -44,7 +44,9 @@ public class PDFTTFStream extends PDFStream { */ protected int output(java.io.OutputStream stream) throws java.io.IOException { - log.debug("Writing " + origLength + " bytes of TTF font data"); + if (log.isDebugEnabled()) { + log.debug("Writing " + origLength + " bytes of TTF font data"); + } int length = super.output(stream); log.debug("Embedded TrueType/OpenType font"); diff --git a/src/java/org/apache/fop/pdf/PDFToUnicodeCMap.java b/src/java/org/apache/fop/pdf/PDFToUnicodeCMap.java index ab3295a46..3d25e28cb 100644 --- a/src/java/org/apache/fop/pdf/PDFToUnicodeCMap.java +++ b/src/java/org/apache/fop/pdf/PDFToUnicodeCMap.java @@ -19,7 +19,8 @@ package org.apache.fop.pdf; -import org.apache.fop.fonts.CIDFont; +import java.io.IOException; +import java.io.Writer; /** * Class representing ToUnicode CMaps. @@ -37,249 +38,251 @@ import org.apache.fop.fonts.CIDFont; public class PDFToUnicodeCMap extends PDFCMap { /** - * handle to read font + * The array of Unicode characters ordered by character code + * (maps from character code to Unicode code point). */ - protected CIDFont cidFont; + protected char[] unicodeCharMap; /** * Constructor. * - * @param cidMetrics the CID font for which this Unicode CMap is built + * @param unicodeCharMap An array of Unicode characters ordered by character code + * (maps from character code to Unicode code point) * @param name One of the registered names found in Table 5.14 in PDF * Reference, Second Edition. * @param sysInfo The attributes of the character collection of the CIDFont. */ - public PDFToUnicodeCMap(CIDFont cidMetrics, String name, PDFCIDSystemInfo sysInfo) { + public PDFToUnicodeCMap(char[] unicodeCharMap, String name, PDFCIDSystemInfo sysInfo) { super(name, sysInfo); - cidFont = cidMetrics; + this.unicodeCharMap = unicodeCharMap; } /** {@inheritDoc} */ - public void fillInPDF(StringBuffer p) { - writeCIDInit(p); - writeCIDSystemInfo(p); - writeVersionTypeName(p); - writeCodeSpaceRange(p); - writeBFEntries(p); - writeWrapUp(p); - add(p.toString()); + protected CMapBuilder createCMapBuilder(Writer writer) { + return new ToUnicodeCMapBuilder(writer); } - - /** {@inheritDoc} */ - protected void writeCIDSystemInfo(StringBuffer p) { - p.append("/CIDSystemInfo\n"); - p.append("<< /Registry (Adobe)\n"); - p.append("/Ordering (UCS)\n"); - p.append("/Supplement 0\n"); - p.append(">> def\n"); - } - - /** {@inheritDoc} */ - protected void writeVersionTypeName(StringBuffer p) { - p.append("/CMapName /Adobe-Identity-UCS def\n"); - p.append("/CMapType 2 def\n"); - } - - /** - * Writes the character mappings for this font. - * @param p StingBuffer to write to - */ - protected void writeBFEntries(StringBuffer p) { - if (cidFont == null) { - return; + + class ToUnicodeCMapBuilder extends CMapBuilder { + + public ToUnicodeCMapBuilder(Writer writer) { + super(writer, null); } - char[] charArray = cidFont.getCharsUsed(); - - if (charArray != null) { - writeBFCharEntries(p, charArray); - writeBFRangeEntries(p, charArray); + /** + * Writes the CMap to a Writer. + * @param writer the writer + * @throws IOException if an I/O error occurs + */ + public void writeCMap() throws IOException { + writeCIDInit(); + writeCIDSystemInfo("Adobe", "UCS", 0); + writeName("Adobe-Identity-UCS"); + writeType("2"); + writeCodeSpaceRange(); + writeBFEntries(); + writeWrapUp(); } - } - - /** - * Writes the entries for single characters of a base font (only characters which cannot be - * expressed as part of a character range). - * @param p StringBuffer to write to - * @param charArray all the characters to map - */ - protected void writeBFCharEntries(StringBuffer p, char[] charArray) { - int totalEntries = 0; - for (int i = 0; i < charArray.length; i++) { - if (!partOfRange(charArray, i)) { - totalEntries++; + + /** + * Writes the character mappings for this font. + * @param p StingBuffer to write to + */ + protected void writeBFEntries() throws IOException { + if (unicodeCharMap != null) { + writeBFCharEntries(unicodeCharMap); + writeBFRangeEntries(unicodeCharMap); } } - if (totalEntries < 1) { - return; - } - int remainingEntries = totalEntries; - int charIndex = 0; - do { - /* Limited to 100 entries in each section */ - int entriesThisSection = Math.min(remainingEntries, 100); - p.append(entriesThisSection + " beginbfchar\n"); - for (int i = 0; i < entriesThisSection; i++) { - /* Go to the next char not in a range */ - while (partOfRange(charArray, charIndex)) { - charIndex++; + + /** + * Writes the entries for single characters of a base font (only characters which cannot be + * expressed as part of a character range). + * @param p StringBuffer to write to + * @param charArray all the characters to map + * @throws IOException + */ + protected void writeBFCharEntries(char[] charArray) throws IOException { + int totalEntries = 0; + for (int i = 0; i < charArray.length; i++) { + if (!partOfRange(charArray, i)) { + totalEntries++; } - p.append("<" + padHexString(Integer.toHexString(charIndex), 4) + "> "); - p.append("<" + padHexString(Integer.toHexString(charArray[charIndex]), 4) + ">\n"); - charIndex++; } - remainingEntries -= entriesThisSection; - p.append("endbfchar\n"); - } while (remainingEntries > 0); - } - - /** - * Writes the entries for character ranges for a base font. - * @param p StringBuffer to write to - * @param charArray all the characters to map - */ - protected void writeBFRangeEntries(StringBuffer p, char[] charArray) { - int totalEntries = 0; - for (int i = 0; i < charArray.length; i++) { - if (startOfRange(charArray, i)) { - totalEntries++; + if (totalEntries < 1) { + return; } - } - if (totalEntries < 1) { - return; - } - int remainingEntries = totalEntries; - int charIndex = 0; - do { - /* Limited to 100 entries in each section */ - int entriesThisSection = Math.min(remainingEntries, 100); - p.append(entriesThisSection + " beginbfrange\n"); - for (int i = 0; i < entriesThisSection; i++) { - /* Go to the next start of a range */ - while (!startOfRange(charArray, charIndex)) { + int remainingEntries = totalEntries; + int charIndex = 0; + do { + /* Limited to 100 entries in each section */ + int entriesThisSection = Math.min(remainingEntries, 100); + writer.write(entriesThisSection + " beginbfchar\n"); + for (int i = 0; i < entriesThisSection; i++) { + /* Go to the next char not in a range */ + while (partOfRange(charArray, charIndex)) { + charIndex++; + } + writer.write("<" + padHexString(Integer.toHexString(charIndex), 4) + "> "); + writer.write("<" + padHexString(Integer.toHexString(charArray[charIndex]), 4) + + ">\n"); charIndex++; } - p.append("<" + padHexString(Integer.toHexString(charIndex), 4) + "> "); - p.append("<" - + padHexString(Integer.toHexString(endOfRange(charArray, charIndex)), 4) - + "> "); - p.append("<" + padHexString(Integer.toHexString(charArray[charIndex]), 4) + ">\n"); - charIndex++; - } - remainingEntries -= entriesThisSection; - p.append("endbfrange\n"); - } while (remainingEntries > 0); - } - - /** - * Find the end of the current range. - * @param charArray The array which is being tested. - * @param startOfRange The index to the array element that is the start of - * the range. - * @return The index to the element that is the end of the range. - */ - private int endOfRange(char[] charArray, int startOfRange) { - int i = startOfRange; - while (i < charArray.length - 1 && sameRangeEntryAsNext(charArray, i)) { - i++; + remainingEntries -= entriesThisSection; + writer.write("endbfchar\n"); + } while (remainingEntries > 0); } - return i; - } - /** - * Determine whether this array element should be part of a bfchar entry or - * a bfrange entry. - * @param charArray The array to be tested. - * @param arrayIndex The index to the array element to be tested. - * @return True if this array element should be included in a range. - */ - private boolean partOfRange(char[] charArray, int arrayIndex) { - if (charArray.length < 2) { - return false; - } - if (arrayIndex == 0) { - return sameRangeEntryAsNext(charArray, 0); - } - if (arrayIndex == charArray.length - 1) { - return sameRangeEntryAsNext(charArray, arrayIndex - 1); - } - if (sameRangeEntryAsNext(charArray, arrayIndex - 1)) { - return true; - } - if (sameRangeEntryAsNext(charArray, arrayIndex)) { - return true; + /** + * Writes the entries for character ranges for a base font. + * @param p StringBuffer to write to + * @param charArray all the characters to map + * @throws IOException + */ + protected void writeBFRangeEntries(char[] charArray) throws IOException { + int totalEntries = 0; + for (int i = 0; i < charArray.length; i++) { + if (startOfRange(charArray, i)) { + totalEntries++; + } + } + if (totalEntries < 1) { + return; + } + int remainingEntries = totalEntries; + int charIndex = 0; + do { + /* Limited to 100 entries in each section */ + int entriesThisSection = Math.min(remainingEntries, 100); + writer.write(entriesThisSection + " beginbfrange\n"); + for (int i = 0; i < entriesThisSection; i++) { + /* Go to the next start of a range */ + while (!startOfRange(charArray, charIndex)) { + charIndex++; + } + writer.write("<" + padHexString(Integer.toHexString(charIndex), 4) + "> "); + writer.write("<" + + padHexString(Integer.toHexString(endOfRange(charArray, charIndex)), 4) + + "> "); + writer.write("<" + padHexString(Integer.toHexString(charArray[charIndex]), 4) + + ">\n"); + charIndex++; + } + remainingEntries -= entriesThisSection; + writer.write("endbfrange\n"); + } while (remainingEntries > 0); } - return false; - } - /** - * Determine whether two bytes can be written in the same bfrange entry. - * @param charArray The array to be tested. - * @param firstItem The first of the two items in the array to be tested. - * The second item is firstItem + 1. - * @return True if both 1) the next item in the array is sequential with - * this one, and 2) the first byte of the character in the first position - * is equal to the first byte of the character in the second position. - */ - private boolean sameRangeEntryAsNext(char[] charArray, int firstItem) { - if (charArray[firstItem] + 1 != charArray[firstItem + 1]) { - return false; - } - if (firstItem / 256 != (firstItem + 1) / 256) { - return false; + /** + * Find the end of the current range. + * @param charArray The array which is being tested. + * @param startOfRange The index to the array element that is the start of + * the range. + * @return The index to the element that is the end of the range. + */ + private int endOfRange(char[] charArray, int startOfRange) { + int i = startOfRange; + while (i < charArray.length - 1 && sameRangeEntryAsNext(charArray, i)) { + i++; + } + return i; } - return true; - } - /** - * Determine whether this array element should be the start of a bfrange - * entry. - * @param charArray The array to be tested. - * @param arrayIndex The index to the array element to be tested. - * @return True if this array element is the beginning of a range. - */ - private boolean startOfRange(char[] charArray, int arrayIndex) { - // Can't be the start of a range if not part of a range. - if (!partOfRange(charArray, arrayIndex)) { + /** + * Determine whether this array element should be part of a bfchar entry or + * a bfrange entry. + * @param charArray The array to be tested. + * @param arrayIndex The index to the array element to be tested. + * @return True if this array element should be included in a range. + */ + private boolean partOfRange(char[] charArray, int arrayIndex) { + if (charArray.length < 2) { + return false; + } + if (arrayIndex == 0) { + return sameRangeEntryAsNext(charArray, 0); + } + if (arrayIndex == charArray.length - 1) { + return sameRangeEntryAsNext(charArray, arrayIndex - 1); + } + if (sameRangeEntryAsNext(charArray, arrayIndex - 1)) { + return true; + } + if (sameRangeEntryAsNext(charArray, arrayIndex)) { + return true; + } return false; } - // If first element in the array, must be start of a range - if (arrayIndex == 0) { + + /** + * Determine whether two bytes can be written in the same bfrange entry. + * @param charArray The array to be tested. + * @param firstItem The first of the two items in the array to be tested. + * The second item is firstItem + 1. + * @return True if both 1) the next item in the array is sequential with + * this one, and 2) the first byte of the character in the first position + * is equal to the first byte of the character in the second position. + */ + private boolean sameRangeEntryAsNext(char[] charArray, int firstItem) { + if (charArray[firstItem] + 1 != charArray[firstItem + 1]) { + return false; + } + if (firstItem / 256 != (firstItem + 1) / 256) { + return false; + } return true; } - // If last element in the array, cannot be start of a range - if (arrayIndex == charArray.length - 1) { - return false; - } - /* - * If part of same range as the previous element is, cannot be start - * of range. + + /** + * Determine whether this array element should be the start of a bfrange + * entry. + * @param charArray The array to be tested. + * @param arrayIndex The index to the array element to be tested. + * @return True if this array element is the beginning of a range. */ - if (sameRangeEntryAsNext(charArray, arrayIndex - 1)) { - return false; + private boolean startOfRange(char[] charArray, int arrayIndex) { + // Can't be the start of a range if not part of a range. + if (!partOfRange(charArray, arrayIndex)) { + return false; + } + // If first element in the array, must be start of a range + if (arrayIndex == 0) { + return true; + } + // If last element in the array, cannot be start of a range + if (arrayIndex == charArray.length - 1) { + return false; + } + /* + * If part of same range as the previous element is, cannot be start + * of range. + */ + if (sameRangeEntryAsNext(charArray, arrayIndex - 1)) { + return false; + } + // Otherwise, this is start of a range. + return true; } - // Otherwise, this is start of a range. - return true; - } - /** - * Prepends the input string with a sufficient number of "0" characters to - * get the returned string to be numChars length. - * @param input The input string. - * @param numChars The minimum characters in the output string. - * @return The padded string. - */ - public static String padHexString(String input, int numChars) { - int length = input.length(); - if (length >= numChars) { - return input; - } - StringBuffer returnString = new StringBuffer(); - for (int i = 1; i <= numChars - length; i++) { - returnString.append("0"); + /** + * Prepends the input string with a sufficient number of "0" characters to + * get the returned string to be numChars length. + * @param input The input string. + * @param numChars The minimum characters in the output string. + * @return The padded string. + */ + private String padHexString(String input, int numChars) { + int length = input.length(); + if (length >= numChars) { + return input; + } + StringBuffer returnString = new StringBuffer(); + for (int i = 1; i <= numChars - length; i++) { + returnString.append("0"); + } + returnString.append(input); + return returnString.toString(); } - returnString.append(input); - return returnString.toString(); - } + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 09c6fbed4..15ed6d98a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -1565,7 +1565,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { startPending = false; } if (!useMultiByte) { - if (ch > 127) { + if (ch < 32 || ch > 127) { pdf.append("\\"); pdf.append(Integer.toOctalString((int) ch)); } else { diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java index 8fb29d302..bbc811b4e 100644 --- a/src/java/org/apache/fop/render/ps/PSFontUtils.java +++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java @@ -31,16 +31,18 @@ import javax.xml.transform.stream.StreamSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +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.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.Typeface; -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; /** * Utility code for font handling in PostScript. @@ -108,8 +110,10 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { } else if ("WinAnsiEncoding".equals(fm.getEncoding())) { redefineFontEncoding(gen, fm.getFontName(), fm.getEncoding()); } else { + /* Don't complain anymore, just use the font's default encoding. gen.commentln("%WARNING: Only WinAnsiEncoding is supported. Font '" + fm.getFontName() + "' asks for: " + fm.getEncoding()); + */ } } gen.commentln("%FOPEndFontReencode"); diff --git a/src/java/org/apache/fop/util/CharUtilities.java b/src/java/org/apache/fop/util/CharUtilities.java index bfcc90a64..4910a371c 100644 --- a/src/java/org/apache/fop/util/CharUtilities.java +++ b/src/java/org/apache/fop/util/CharUtilities.java @@ -68,7 +68,10 @@ public class CharUtilities { public static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF'; /** soft hyphen */ public static final char SOFT_HYPHEN = '\u00AD'; - + /** missing ideograph */ + public static final char MISSING_IDEOGRAPH = '\u25A1'; + /** Unicode value indicating the the character is "not a character". */ + public static final char NOT_A_CHARACTER = '\uFFFF'; /** * Utility class: Constructor prevents instantiating when subclassed. diff --git a/status.xml b/status.xml index 7926fe379..305fe8c7c 100644 --- a/status.xml +++ b/status.xml @@ -28,6 +28,10 @@ + + Added support for unusual font encodings (like for Symbol or Cyrillic fonts) of Type 1 + fonts in PDF and PostScript output. + Moved to the FO tree stage the check for break-before/after on table-row while spanning in progress. diff --git a/test/java/org/apache/fop/render/pdf/PDFCMapTestCase.java b/test/java/org/apache/fop/render/pdf/PDFCMapTestCase.java index a7ea1a2db..eb34f70b8 100644 --- a/test/java/org/apache/fop/render/pdf/PDFCMapTestCase.java +++ b/test/java/org/apache/fop/render/pdf/PDFCMapTestCase.java @@ -19,14 +19,16 @@ package org.apache.fop.render.pdf; -import org.apache.fop.pdf.PDFCMap; +import java.io.StringWriter; import junit.framework.TestCase; +import org.apache.fop.pdf.CMapBuilder; + /** Simple sanity test of the PDFCmap class */ public class PDFCMapTestCase extends TestCase { - public void testPDFCMapFillInPDF() { + public void testPDFCMapFillInPDF() throws Exception { final String EOL = "\n"; final String expected = "%!PS-Adobe-3.0 Resource-CMap" + EOL @@ -59,10 +61,10 @@ public class PDFCMapTestCase extends TestCase { +"%%EOF" + EOL ; - final PDFCMap m = new PDFCMap("test", null); - final StringBuffer b = new StringBuffer(); - m.fillInPDF(b); - final String actual = b.toString(); + final StringWriter w = new StringWriter(); + final CMapBuilder builder = new CMapBuilder(w, "test"); + builder.writeCMap(); + final String actual = w.getBuffer().toString(); assertEquals("PDFCMap output matches expected PostScript code", expected, actual); }