diff options
37 files changed, 2272 insertions, 934 deletions
diff --git a/lib/xmlgraphics-commons-1.3svn.jar b/lib/xmlgraphics-commons-1.3svn.jar Binary files differindex 0abad1c33..80927dd7e 100644 --- a/lib/xmlgraphics-commons-1.3svn.jar +++ b/lib/xmlgraphics-commons-1.3svn.jar 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 { + +<xsl:apply-templates mode="constant"/> + + 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 { } </xsl:template> + <xsl:template match="encoding" mode="constant"> public static final String <xsl:value-of select="@constant"/> = "<xsl:value-of select="@id"/>";</xsl:template> + <xsl:template match="encoding" mode="get"> - else if (encoding.equals("<xsl:value-of select="@id"/>")) { - mapping = new CodePointMapping(enc<xsl:value-of select="@id"/>); - mappings.put("<xsl:value-of select="@id"/>", mapping); + else if (encoding.equals(<xsl:value-of select="@constant"/>)) { + mapping = new CodePointMapping(<xsl:value-of select="@constant"/>, enc<xsl:value-of select="@id"/>); + mappings.put(<xsl:value-of select="@constant"/>, mapping); return mapping; } </xsl:template> 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 @@ <!DOCTYPE encoding-set [ <!ELEMENT encoding-set (encoding+)> <!ELEMENT encoding (glyph+)> - <!ATTLIST encoding id ID #REQUIRED glyphlist CDATA "AGL"> + <!ATTLIST encoding id ID #REQUIRED constant CDATA #REQUIRED glyphlist CDATA "AGL"> <!ELEMENT glyph EMPTY> <!ATTLIST glyph codepoint CDATA #REQUIRED name CDATA #REQUIRED> ]> <encoding-set> - <encoding id='StandardEncoding' glyphlist='AGL'> + <encoding id='StandardEncoding' constant="STANDARD_ENCODING" glyphlist='AGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='exclam'/> <glyph codepoint='22' name='quotedbl'/> @@ -175,7 +175,7 @@ <glyph codepoint='fa' name='oe'/> <glyph codepoint='fb' name='germandbls'/> </encoding> - <encoding id='ISOLatin1Encoding' glyphlist='AGL'> + <encoding id='ISOLatin1Encoding' constant="ISOLATIN1_ENCODING" glyphlist='AGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='exclam'/> <glyph codepoint='22' name='quotedbl'/> @@ -377,7 +377,7 @@ <glyph codepoint='fe' name='thorn'/> <glyph codepoint='ff' name='ydieresis'/> </encoding> - <encoding id='CEEncoding' glyphlist='AGL'> + <encoding id='CEEncoding' constant="CE_ENCODING" glyphlist='AGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='exclam'/> <glyph codepoint='22' name='quotedbl'/> @@ -594,7 +594,7 @@ <glyph codepoint='fe' name='tcommaaccent'/> <glyph codepoint='ff' name='dotaccent'/> </encoding> - <encoding id='MacRomanEncoding' glyphlist='AGL'> + <encoding id='MacRomanEncoding' constant="MAC_ROMAN_ENCODING" glyphlist='AGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='exclam'/> <glyph codepoint='22' name='quotedbl'/> @@ -803,7 +803,7 @@ <glyph codepoint='b4' name='yen'/> <glyph codepoint='7a' name='z'/> </encoding> - <encoding id='WinAnsiEncoding' glyphlist='AGL'> + <encoding id='WinAnsiEncoding' constant="WIN_ANSI_ENCODING" glyphlist='AGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='exclam'/> <glyph codepoint='22' name='quotedbl'/> @@ -1021,7 +1021,7 @@ <glyph codepoint='fe' name='thorn'/> <glyph codepoint='ff' name='ydieresis'/> </encoding> - <encoding id='PDFDocEncoding' glyphlist='AGL'> + <encoding id='PDFDocEncoding' constant="PDF_DOC_ENCODING" glyphlist='AGL'> <glyph codepoint='18' name='breve'/> <glyph codepoint='19' name='caron'/> <glyph codepoint='1a' name='circumflex'/> @@ -1252,7 +1252,7 @@ <glyph codepoint='fe' name='thorn'/> <glyph codepoint='ff' name='ydieresis'/> </encoding> - <encoding id='SymbolEncoding' glyphlist='AGL'> + <encoding id='SymbolEncoding' constant="SYMBOL_ENCODING" glyphlist='AGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='exclam'/> <glyph codepoint='22' name='universal'/> @@ -1443,7 +1443,7 @@ <glyph codepoint='fd' name='bracerightmid'/> <glyph codepoint='fe' name='bracerightbt'/> </encoding> - <encoding id='ZapfDingbatsEncoding' glyphlist='ZGL'> + <encoding id='ZapfDingbatsEncoding' constant="ZAPF_DINGBATS_ENCODING" glyphlist='ZGL'> <glyph codepoint='20' name='space'/> <glyph codepoint='21' name='a1'/> <glyph codepoint='22' name='a2'/> 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<Integer, Map<Integer, Integer>, 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,86 +34,79 @@ 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); } @@ -119,15 +114,43 @@ public class SingleByteFont extends CustomFont { /* ---- 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 * @param width the width of the character */ 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<AFMCharMetrics> + private Map charNameToMetrics = new java.util.HashMap(); + //Map<String, AFMCharMetrics> + + private Map kerningMap; + //Map<String, Map<String, Dimension>> + + /** + * 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> <FFFF>\n"); + writer.write("endcodespacerange\n"); + } + + protected void writeCIDRange() throws IOException { + writer.write("1 begincidrange\n"); + writer.write("<0000> <FFFF> 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. @@ -395,15 +397,6 @@ public class PDFCMap extends PDFStream { } /** - * 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 * * @param base the name of 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> <FFFF>\n"); - p.append("endcodespacerange\n"); - } - - protected void writeCIDRange(StringBuffer p) { - p.append("1 begincidrange\n"); - p.append("<0000> <FFFF> 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; * <p> * 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 <code>null</code> 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,20 +45,46 @@ 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 * * @param fontname the internal name for the font @@ -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. - * <p> - * 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; * <p> * 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 @@ -28,26 +28,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 * * @param fontname the internal name for the font @@ -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; * <p> * 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 @@ -32,26 +32,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 * * @param fontname the internal name for the font @@ -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 @@ <changes> <release version="FOP Trunk"> + <action context="Fonts" dev="JM" type="add"> + Added support for unusual font encodings (like for Symbol or Cyrillic fonts) of Type 1 + fonts in PDF and PostScript output. + </action> <action context="Layout" dev="VH" type="fix" fixes-bug="44321"> 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); } |