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];
- }
+ char unicode = (char)table[i + 1];
+ if (unicode < 256) {
+ if (latin1Map[unicode] == 0) {
+ latin1Map[unicode] = (char) table[i];
+ }
+ } else {
+ ++nonLatin1;
+ }
+ if (unicodeMap[table[i]] == CharUtilities.NOT_A_CHARACTER) {
+ unicodeMap[table[i]] = unicode;
+ }
}
characters = new char[nonLatin1];
codepoints = new char[nonLatin1];
m.useGeneric(genericSpace);
corr = new SpacePropertyMaker(m);
corr.setCorresponding(PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM, PR_MARGIN_LEFT);
- corr.setUseParent(true);
+ corr.setUseParent(false);
corr.setRelative(true);
addPropertyMaker("space-after", m);
m.setDefault("0pt");
IndentPropertyMaker sCorr = new IndentPropertyMaker(m);
sCorr.setCorresponding(PR_MARGIN_LEFT, PR_MARGIN_RIGHT, PR_MARGIN_TOP);
- sCorr.setUseParent(true);
+ sCorr.setUseParent(false);
sCorr.setRelative(true);
sCorr.setPaddingCorresponding(new int[] {
PR_PADDING_LEFT, PR_PADDING_RIGHT, PR_PADDING_TOP
m.setDefault("0pt");
IndentPropertyMaker eCorr = new IndentPropertyMaker(m);
eCorr.setCorresponding(PR_MARGIN_RIGHT, PR_MARGIN_LEFT, PR_MARGIN_BOTTOM);
- eCorr.setUseParent(true);
+ eCorr.setUseParent(false);
eCorr.setRelative(true);
eCorr.setPaddingCorresponding(new int[] {
PR_PADDING_RIGHT, PR_PADDING_LEFT, PR_PADDING_BOTTOM
package org.apache.fop.fonts.type1;
+import java.awt.geom.RectangularShape;
+
/**
* Holds the metrics of a single character from an AFM file.
*/
public class AFMCharMetrics {
- private int charCode;
+ private int charCode = -1;
private String unicodeChars;
private String charName;
private double widthX;
private double widthY;
+ private RectangularShape bBox;
/**
* Returns the character code.
- * @return the charCode
+ * @return the charCode (-1 if not part of the encoding)
*/
public int getCharCode() {
return charCode;
}
+ /**
+ * Indicates whether the character has a character code, i.e. is part of the default encoding.
+ * @return true if there is a character code.
+ */
+ public boolean hasCharCode() {
+ return charCode >= 0;
+ }
+
/**
* Sets the character code.
* @param charCode the charCode to set
this.widthY = widthY;
}
+ /**
+ * Returns the character's bounding box.
+ * @return the bounding box (or null if it isn't available)
+ */
+ public RectangularShape getBBox() {
+ return bBox;
+ }
+
+ /**
+ * Sets the character's bounding box.
+ * @param box the bounding box
+ */
+ public void setBBox(RectangularShape box) {
+ bBox = box;
+ }
+
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer("AFM Char: ");
package org.apache.fop.fonts.type1;
+import java.awt.geom.Dimension2D;
import java.awt.geom.RectangularShape;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
//List<AFMCharMetrics>
private Map charNameToMetrics = new java.util.HashMap();
//Map<String, AFMCharMetrics>
+ private int firstChar = -1;
+ private int lastChar = -1;
private Map kerningMap;
- //Map<String, Map<String, Dimension>>
+ //Map<String, Map<String, Dimension2D>>
/**
* Default constructor.
if (name != null) {
String u = Glyphs.getUnicodeCodePointsForGlyphName(metrics.getCharName());
if (u != null) {
+ if (u.length() > 1) {
+ //Lower values (ex. space) are most probably more interesting than
+ //higher values (ex. non-break-space), so sort just to be sure:
+ char[] chars = u.toCharArray();
+ Arrays.sort(chars);
+ u = String.valueOf(chars);
+ }
metrics.setUnicodeChars(u);
}
} else {
if (name != null) {
this.charNameToMetrics.put(name, metrics);
}
+ int idx = metrics.getCharCode();
+ if (idx >= 0) { //Only if the character is part of the encoding
+ if (firstChar < 0 || idx < firstChar) {
+ firstChar = idx;
+ }
+ if (lastChar < 0 || idx > lastChar) {
+ lastChar = idx;
+ }
+ }
}
/**
return this.charMetrics.size();
}
+ /**
+ * Returns the first character index in the encoding that has a glyph.
+ * @return the first character index with a glyph
+ */
+ public int getFirstChar() {
+ return this.firstChar;
+ }
+
+ /**
+ * Returns the last character index in the encoding that has a glyph.
+ * @return the last character index with a glyph
+ */
+ public int getLastChar() {
+ return this.lastChar;
+ }
+
/**
* Returns the character metrics associated with the character name.
* @param name the character name
entries.put(name2, new Dimension2DDouble(kx, 0));
}
+ /**
+ * Indicates whether the font has kerning information.
+ * @return true if there is kerning information
+ */
+ public boolean hasKerning() {
+ return this.kerningMap != null;
+ }
+
+ /**
+ * Creates and returns a kerning map for writing mode 0 (ltr) with character codes.
+ * @return the kerning map or null if there is no kerning information.
+ */
+ public Map createXKerningMapEncoded() {
+ if (!hasKerning()) {
+ return null;
+ }
+ Map m = new java.util.HashMap();
+ Iterator iterFrom = this.kerningMap.entrySet().iterator();
+ while (iterFrom.hasNext()) {
+ Map.Entry entryFrom = (Map.Entry)iterFrom.next();
+ String name1 = (String)entryFrom.getKey();
+ AFMCharMetrics chm1 = getChar(name1);
+ if (!chm1.hasCharCode()) {
+ continue;
+ }
+ Map container = null;
+ Map entriesTo = (Map)entryFrom.getValue();
+ Iterator iterTo = entriesTo.entrySet().iterator();
+ while (iterTo.hasNext()) {
+ Map.Entry entryTo = (Map.Entry)iterTo.next();
+ String name2 = (String)entryTo.getKey();
+ AFMCharMetrics chm2 = getChar(name2);
+ if (!chm2.hasCharCode()) {
+ continue;
+ }
+ if (container == null) {
+ Integer k1 = new Integer(chm1.getCharCode());
+ container = (Map)m.get(k1);
+ if (container == null) {
+ container = new java.util.HashMap();
+ m.put(k1, container);
+ }
+ }
+ Dimension2D dim = (Dimension2D)entryTo.getValue();
+ container.put(new Integer(chm2.getCharCode()),
+ new Integer((int)Math.round(dim.getWidth())));
+ }
+ }
+ return m;
+ }
+
/** {@inheritDoc} */
public String toString() {
return "AFM: " + getFullName();
private static final String W0 = "W0";
private static final String W1 = "W1";
private static final String N = "N";
+ private static final String B = "B";
private static final String START_TRACK_KERN = "StartTrackKern";
private static final String END_TRACK_KERN = "EndTrackKern";
//private static final String START_KERN_PAIRS = "StartKernPairs";
VALUE_PARSERS.put(W0, new NotImplementedYet(W0));
VALUE_PARSERS.put(W1, new NotImplementedYet(W1));
VALUE_PARSERS.put(N, new StringSetter("CharName"));
+ VALUE_PARSERS.put(B, new CharBBox());
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));
private static class FontBBox extends AbstractValueHandler {
public void parse(String line, int startpos, Stack stack) throws IOException {
+ Rectangle rect = parseBBox(line, startpos);
+
AFMFile afm = (AFMFile)stack.peek();
+ afm.setFontBBox(rect);
+ }
+
+ protected Rectangle parseBBox(String line, int startpos) {
Rectangle rect = new Rectangle();
int endpos;
v = Integer.parseInt(line.substring(startpos, endpos));
rect.height = v - rect.y;
startpos = skipToNonWhiteSpace(line, endpos);
-
- afm.setFontBBox(rect);
+ return rect;
}
}
+ private static class CharBBox extends FontBBox {
+ public void parse(String line, int startpos, Stack stack) throws IOException {
+ Rectangle rect = parseBBox(line, startpos);
+
+ AFMCharMetrics metrics = (AFMCharMetrics)stack.peek();
+ metrics.setBBox(rect);
+ }
+ }
+
private static class IsBaseFont extends AbstractValueHandler {
public void parse(String line, int startpos, Stack stack) throws IOException {
if (getBooleanValue(line, startpos).booleanValue()) {
package org.apache.fop.fonts.type1;
+import java.awt.geom.RectangularShape;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
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;
}
}
singleFont = new SingleByteFont();
singleFont.setFontType(FontType.TYPE1);
- if (pfm.getCharSet() >= 0 && pfm.getCharSet() <= 2) {
- singleFont.setEncoding(pfm.getCharSetName() + "Encoding");
- } else {
- log.warn("The PFM reports an unsupported encoding ("
- + pfm.getCharSetName() + "). The font may not work as expected.");
- singleFont.setEncoding("WinAnsiEncoding"); //Try fallback, no guarantees!
- }
singleFont.setResolver(this.resolver);
+ singleFont.setEmbedFileName(this.fontFileURI);
returnFont = singleFont;
- //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();
CodePointMapping mapping = buildCustomEncoding(effEncodingName, afm);
singleFont.setEncoding(mapping);
}
+ } else {
+ if (pfm.getCharSet() >= 0 && pfm.getCharSet() <= 2) {
+ singleFont.setEncoding(pfm.getCharSetName() + "Encoding");
+ } else {
+ log.warn("The PFM reports an unsupported encoding ("
+ + pfm.getCharSetName() + "). The font may not work as expected.");
+ singleFont.setEncoding("WinAnsiEncoding"); //Try fallback, no guarantees!
+ }
+ }
+
+ //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);
}
//Basic metrics
if (afm.getDescender() != null) {
returnFont.setDescender(afm.getDescender().intValue());
}
+
returnFont.setFontBBox(afm.getFontBBoxAsIntArray());
if (afm.getStdVW() != null) {
returnFont.setStemV(afm.getStdVW().intValue());
returnFont.setItalicAngle(pfm.getItalicAngle());
}
if (pfm != null) {
- if (returnFont.getCapHeight() == 0) {
- returnFont.setCapHeight(pfm.getCapHeight());
+ //Sometimes the PFM has these metrics while the AFM doesn't (ex. Symbol)
+ returnFont.setCapHeight(pfm.getCapHeight());
+ returnFont.setXHeight(pfm.getXHeight());
+ returnFont.setAscender(pfm.getLowerCaseAscent());
+ returnFont.setDescender(pfm.getLowerCaseDescent());
+ }
+
+ //Fallbacks when some crucial font metrics aren't available
+ //(the following are all optional in AFM, but FontBBox is always available)
+ if (returnFont.getXHeight(1) == 0) {
+ int xHeight = 0;
+ AFMCharMetrics chm = afm.getChar("x");
+ if (chm != null) {
+ RectangularShape rect = chm.getBBox();
+ if (rect != null) {
+ xHeight = (int)Math.round(rect.getMinX());
+ }
}
- if (returnFont.getXHeight(1) == 0) {
- returnFont.setXHeight(pfm.getXHeight());
+ if (xHeight == 0) {
+ xHeight = Math.round(returnFont.getFontBBox()[3] * 0.6f);
}
- if (returnFont.getAscender() == 0) {
- returnFont.setAscender(pfm.getLowerCaseAscent());
+ returnFont.setXHeight(xHeight);
+ }
+ if (returnFont.getAscender() == 0) {
+ int asc = 0;
+ AFMCharMetrics chm = afm.getChar("d");
+ if (chm != null) {
+ RectangularShape rect = chm.getBBox();
+ if (rect != null) {
+ asc = (int)Math.round(rect.getMinX());
+ }
}
- if (returnFont.getDescender() == 0) {
- returnFont.setDescender(pfm.getLowerCaseDescent());
+ if (asc == 0) {
+ asc = Math.round(returnFont.getFontBBox()[3] * 0.9f);
}
+ returnFont.setAscender(asc);
}
- returnFont.setFirstChar(pfm.getFirstChar());
- returnFont.setLastChar(pfm.getLastChar());
- returnFont.setFlags(pfm.getFlags());
- returnFont.setMissingWidth(0);
- for (short i = pfm.getFirstChar(); i <= pfm.getLastChar(); i++) {
- singleFont.setWidth(i, pfm.getCharWidth(i));
+ if (returnFont.getDescender() == 0) {
+ int desc = 0;
+ AFMCharMetrics chm = afm.getChar("p");
+ if (chm != null) {
+ RectangularShape rect = chm.getBBox();
+ if (rect != null) {
+ desc = (int)Math.round(rect.getMinX());
+ }
+ }
+ if (desc == 0) {
+ desc = returnFont.getFontBBox()[1];
+ }
+ returnFont.setDescender(desc);
+ }
+ if (returnFont.getCapHeight() == 0) {
+ returnFont.setCapHeight(returnFont.getAscender());
+ }
+
+ if (afm != null) {
+ returnFont.setFirstChar(afm.getFirstChar());
+ returnFont.setLastChar(afm.getLastChar());
+ Iterator iter = afm.getCharMetrics().iterator();
+ while (iter.hasNext()) {
+ AFMCharMetrics chm = (AFMCharMetrics)iter.next();
+ if (chm.hasCharCode()) {
+ singleFont.setWidth(chm.getCharCode(), (int)Math.round(chm.getWidthX()));
+ }
+ }
+ returnFont.replaceKerningMap(afm.createXKerningMapEncoded());
+ } else {
+ returnFont.setFirstChar(pfm.getFirstChar());
+ returnFont.setLastChar(pfm.getLastChar());
+ returnFont.setFlags(pfm.getFlags());
+ for (short i = pfm.getFirstChar(); i <= pfm.getLastChar(); i++) {
+ singleFont.setWidth(i, pfm.getCharWidth(i));
+ }
+ returnFont.replaceKerningMap(pfm.getKerning());
}
- returnFont.replaceKerningMap(pfm.getKerning());
- singleFont.setEmbedFileName(this.fontFileURI);
}
private CodePointMapping buildCustomEncoding(String encodingName, AFMFile afm) {