From: Jeremias Maerki Date: Thu, 14 Feb 2008 10:41:26 +0000 (+0000) Subject: Fix problem with alternate Unicode code point overriding existing better ones in... X-Git-Tag: fop-0_95beta~68 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e1c8b0065b1d5b8456546abbc9d7e040123c380a;p=xmlgraphics-fop.git Fix problem with alternate Unicode code point overriding existing better ones in CodePointMapping (ex. a char code for NBSP was used in place of SPACE for non-standard encodings). Made PFM completely optional if an AFM is available. Widths and Kerning are now also read from the AFM. Fallbacks for missing values are in place. If both AFM and PFM are available, both are used to get the best possible result for certain metrics. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@627702 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/codegen/fonts/code-point-mapping.xsl b/src/codegen/fonts/code-point-mapping.xsl index 80b62dd17..c82ca0104 100644 --- a/src/codegen/fonts/code-point-mapping.xsl +++ b/src/codegen/fonts/code-point-mapping.xsl @@ -63,14 +63,17 @@ public class CodePointMapping { 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]; diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index b78ec49c4..9910f1ce7 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -1153,7 +1153,7 @@ public final class FOPropertyMapping implements Constants { 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); @@ -1163,7 +1163,7 @@ public final class FOPropertyMapping implements Constants { 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 @@ -1179,7 +1179,7 @@ public final class FOPropertyMapping implements Constants { 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 diff --git a/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java b/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java index 918d9ee08..fbbff567e 100644 --- a/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java +++ b/src/java/org/apache/fop/fonts/type1/AFMCharMetrics.java @@ -19,26 +19,37 @@ 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 @@ -113,6 +124,22 @@ public class AFMCharMetrics { 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: "); diff --git a/src/java/org/apache/fop/fonts/type1/AFMFile.java b/src/java/org/apache/fop/fonts/type1/AFMFile.java index af912975c..0b7b8d3c2 100644 --- a/src/java/org/apache/fop/fonts/type1/AFMFile.java +++ b/src/java/org/apache/fop/fonts/type1/AFMFile.java @@ -19,8 +19,11 @@ 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; @@ -56,9 +59,11 @@ public class AFMFile { //List private Map charNameToMetrics = new java.util.HashMap(); //Map + private int firstChar = -1; + private int lastChar = -1; private Map kerningMap; - //Map> + //Map> /** * Default constructor. @@ -314,6 +319,13 @@ public class AFMFile { 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 { @@ -325,6 +337,15 @@ public class AFMFile { 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; + } + } } /** @@ -335,6 +356,22 @@ public class AFMFile { 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 @@ -370,6 +407,57 @@ public class AFMFile { 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(); diff --git a/src/java/org/apache/fop/fonts/type1/AFMParser.java b/src/java/org/apache/fop/fonts/type1/AFMParser.java index 4d852058c..bb7ea3d30 100644 --- a/src/java/org/apache/fop/fonts/type1/AFMParser.java +++ b/src/java/org/apache/fop/fonts/type1/AFMParser.java @@ -73,6 +73,7 @@ public class AFMParser { 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"; @@ -126,6 +127,7 @@ public class AFMParser { 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)); @@ -497,7 +499,13 @@ public class AFMParser { 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; @@ -518,11 +526,19 @@ public class AFMParser { 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()) { diff --git a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java index f5b04442b..57383dc76 100644 --- a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java +++ b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java @@ -19,6 +19,7 @@ package org.apache.fop.fonts.type1; +import java.awt.geom.RectangularShape; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; @@ -107,11 +108,6 @@ public class Type1FontLoader extends FontLoader { 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; } @@ -122,33 +118,10 @@ public class Type1FontLoader extends FontLoader { } 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(); @@ -169,6 +142,31 @@ public class Type1FontLoader extends FontLoader { 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 @@ -185,6 +183,7 @@ public class Type1FontLoader extends FontLoader { if (afm.getDescender() != null) { returnFont.setDescender(afm.getDescender().intValue()); } + returnFont.setFontBBox(afm.getFontBBoxAsIntArray()); if (afm.getStdVW() != null) { returnFont.setStemV(afm.getStdVW().intValue()); @@ -198,28 +197,81 @@ public class Type1FontLoader extends FontLoader { 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) {