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];
}
}
+ public String getName() {
+ return this.name;
+ }
+
public final char mapChar(char c) {
if (c < 256) {
return latin1Map[c];
}
}
+ 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) {
}
</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>
<!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'/>
<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'/>
<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'/>
<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'/>
<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'/>
<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'/>
<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'/>
<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'/>
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);
* @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;
}
/**
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;
+ }
+ }
}
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;
/** 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 */
/**
* 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;
}
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) {
*/
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();
*/
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);
/**
package org.apache.fop.fonts;
+import java.util.Set;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private CodePointMapping mapping;
- private String encoding = "WinAnsiEncoding";
-
private int[] width = null;
+ private Set warnedChars;
+
/**
* Main constructor.
*/
public SingleByteFont() {
- updateMapping();
+ setEncoding(CodePointMapping.WIN_ANSI_ENCODING);
}
- /**
- * Updates the mapping variable based on the encoding.
- */
- protected void updateMapping() {
- try {
- mapping = CodePointMapping.getMapping(getEncoding());
- } catch (UnsupportedOperationException e) {
- log.error("Font '" + super.getFontName() + "': " + e.getMessage());
- }
- }
-
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public boolean isEmbeddable() {
return (getEmbedFileName() == null && getEmbedResourceName() == null) ? false
: true;
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public String getEncoding() {
- return encoding;
+ return this.mapping.getName();
}
/**
- * Sets the encoding of the font.
- * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding")
+ * Returns the code point mapping (encoding) of this font.
+ * @return the code point mapping
*/
- public void setEncoding(String encoding) {
- this.encoding = encoding;
- updateMapping();
+ public CodePointMapping getCodePointMapping() {
+ return this.mapping;
}
-
- /**
- * {@inheritDoc}
- */
+
+ /** {@inheritDoc} */
public int getWidth(int i, int size) {
- return size * width[i];
+ int idx = i - getFirstChar();
+ if (idx >= 0 && idx < width.length) {
+ return size * width[i - getFirstChar()];
+ } else {
+ return 0;
+ }
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public int[] getWidths() {
int[] arr = new int[width.length];
System.arraycopy(width, 0, arr, 0, width.length - 1);
return arr;
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public char mapChar(char c) {
notifyMapOperation();
char d = mapping.mapChar(c);
if (d != 0) {
return d;
} else {
- log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c)
- + ") not available in font " + getFontName());
+ Character ch = new Character(c);
+ if (warnedChars == null) {
+ warnedChars = new java.util.HashSet();
+ }
+ if (warnedChars.size() < 8 && !warnedChars.contains(ch)) {
+ warnedChars.add(ch);
+ if (warnedChars.size() == 8) {
+ log.warn("Many requested glyphs are not available in font " + getFontName());
+ } else {
+ log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c)
+ + ", " + Glyphs.charToGlyphName(c)
+ + ") not available in font " + getFontName());
+ }
+ }
return '#';
}
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public boolean hasChar(char c) {
return (mapping.mapChar(c) > 0);
}
/* ---- single byte font specific setters --- */
+ /**
+ * Updates the mapping variable based on the encoding.
+ * @param encoding the name of the encoding
+ */
+ protected void updateMapping(String encoding) {
+ try {
+ this.mapping = CodePointMapping.getMapping(encoding);
+ } catch (UnsupportedOperationException e) {
+ log.error("Font '" + super.getFontName() + "': " + e.getMessage());
+ }
+ }
+
+ /**
+ * Sets the encoding of the font.
+ * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding")
+ */
+ public void setEncoding(String encoding) {
+ updateMapping(encoding);
+ }
+
+ /**
+ * Sets the encoding of the font.
+ * @param encoding the encoding information
+ */
+ public void setEncoding(CodePointMapping encoding) {
+ this.mapping = encoding;
+ }
+
/**
* Sets a width for a character.
* @param index index of the character
*/
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;
}
}
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;
/**
* 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");
multiFont.setBFEntries(bfentries);
copyKerning(ttf, true);
multiFont.setEmbedFileName(this.fontFileURI);
- loaded = true;
}
/**
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
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;
*/
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) {
}
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);
+ }
}
--- /dev/null
+/*
+ * 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
*/
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);
}
// 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
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);
}
}
import java.io.IOException;
import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
/**
* Class representing the CMap encodings.
this.wMode = mode;
}
- /**
- * Add the contents of this pdf object to the PDF stream.
- */
- public void addContents() {
- StringBuffer p = new StringBuffer();
- fillInPDF(p);
- add(p.toString());
- }
-
/**
* set the base CMap
*
}
/**
- * 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);
}
}
* @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);
}
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.
*
* 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
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;
}
if (descriptor == null) {
+ //Usually Base 14 fonts
PDFFont font = new PDFFont(fontname, FontType.TYPE1, basefont, encoding);
getDocument().registerObject(font);
return font;
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) {
(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));
((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;
* <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
*
*/
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
*
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());
}
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
- }
-
}
* <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
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);
}
/**
*/
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);
+ }
}
/**
* @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;
}
/**
* @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()
fillInPDF(p);
p.append(" >>\nendobj\n");
return p.toString();
- }
+ }*/
/**
* Fill in the specifics for the font's descriptor.
* 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
- }
+ }*/
}
*/
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
*
/* generic creation of PDF object */
super(fontname, subtype, basefont, encoding);
-
- this.descriptor = null;
}
/**
*/
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);
}
/**
* @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} */
}
}
- /**
- * {@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());
- }
- }
-
}
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);
}
}
* <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
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);
}
/**
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);
}
/**
* @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}));
}
/**
* @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);
}
}
*/
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
*
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);
}
/**
/* 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);
}
/**
* @param bbox bounding box for the font
*/
public void setFontBBox(PDFRectangle bbox) {
- this.fontBBox = bbox;
+ put("FontBBox", bbox);
}
/**
* @param matrix the transformation matrix for the font
*/
public void setFontMatrix(PDFArray matrix) {
- this.fontMatrix = matrix;
+ put("FontMatrix", matrix);
}
/**
* @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);
}
}
*/
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);
}
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;
}
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
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());
+ }
}
import java.io.IOException;
import java.io.OutputStream;
-// FOP
import org.apache.fop.fonts.type1.PFBData;
/**
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");
*/
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");
package org.apache.fop.pdf;
-import org.apache.fop.fonts.CIDFont;
+import java.io.IOException;
+import java.io.Writer;
/**
* Class representing ToUnicode CMaps.
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();
- }
+ }
+
}
startPending = false;
}
if (!useMultiByte) {
- if (ch > 127) {
+ if (ch < 32 || ch > 127) {
pdf.append("\\");
pdf.append(Integer.toOctalString((int) ch));
} else {
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.
} 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");
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.
<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.
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
+"%%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);
}