| /* |
| * 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.Dimension2D; |
| import java.awt.geom.RectangularShape; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.xmlgraphics.java2d.Dimension2DDouble; |
| |
| import org.apache.fop.fonts.NamedCharacter; |
| import org.apache.fop.fonts.SingleByteEncoding; |
| |
| /** |
| * Represents the contents of a Type 1 AFM font metrics file. |
| */ |
| public class AFMFile { |
| |
| /** logging instance */ |
| private static final Log LOG = LogFactory.getLog(AFMFile.class); |
| |
| 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<AFMCharMetrics> charMetrics = new java.util.ArrayList<AFMCharMetrics>(); |
| private Map<String, AFMCharMetrics> charNameToMetrics |
| = new java.util.HashMap<String, AFMCharMetrics>(); |
| private int firstChar = -1; |
| private int lastChar = -1; |
| |
| private Map<String, Map<String, Dimension2D>> kerningMap; |
| |
| /** |
| * 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.getUnicodeSequence() == null && name.equals(".notdef")) { |
| //Ignore as no Unicode assignment is possible |
| return; |
| } |
| this.charMetrics.add(metrics); |
| 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; |
| } |
| } |
| } |
| |
| /** |
| * Returns the number of character available for this font. |
| * @return the number of character |
| */ |
| public int getCharCount() { |
| 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 |
| * @return the character metrics or null if there's no such character |
| */ |
| public AFMCharMetrics getChar(String name) { |
| return this.charNameToMetrics.get(name); |
| } |
| |
| /** |
| * Returns the list of AFMCharMetrics instances representing all the available characters. |
| * @return a List of AFMCharMetrics instances |
| */ |
| public List<AFMCharMetrics> 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<String, Map<String, Dimension2D>>(); |
| } |
| Map<String, Dimension2D> entries = this.kerningMap.get(name1); |
| if (entries == null) { |
| entries = new java.util.HashMap<String, Dimension2D>(); |
| this.kerningMap.put(name1, entries); |
| } |
| 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<Integer, Map<Integer, Integer>> createXKerningMapEncoded() { |
| if (!hasKerning()) { |
| return null; |
| } |
| Map<Integer, Map<Integer, Integer>> m |
| = new java.util.HashMap<Integer, Map<Integer, Integer>>(); |
| for (Map.Entry<String, Map<String, Dimension2D>> entryFrom : this.kerningMap.entrySet()) { |
| String name1 = entryFrom.getKey(); |
| AFMCharMetrics chm1 = getChar(name1); |
| if (chm1 == null || !chm1.hasCharCode()) { |
| continue; |
| } |
| Map<Integer, Integer> container = null; |
| Map<String, Dimension2D> entriesTo = entryFrom.getValue(); |
| for (Map.Entry<String, Dimension2D> entryTo : entriesTo.entrySet()) { |
| String name2 = entryTo.getKey(); |
| AFMCharMetrics chm2 = getChar(name2); |
| if (chm2 == null || !chm2.hasCharCode()) { |
| continue; |
| } |
| if (container == null) { |
| Integer k1 = Integer.valueOf(chm1.getCharCode()); |
| container = m.get(k1); |
| if (container == null) { |
| container = new java.util.HashMap<Integer, Integer>(); |
| m.put(k1, container); |
| } |
| } |
| Dimension2D dim = entryTo.getValue(); |
| container.put(Integer.valueOf(chm2.getCharCode()), |
| Integer.valueOf((int)Math.round(dim.getWidth()))); |
| } |
| } |
| return m; |
| } |
| |
| /** |
| * The character codes in an AFM cannot always be trusted to be the same values as in the |
| * font's primary encoding. Therefore, we provide a way to override this primary encoding. |
| * @param encoding the encoding to replace the one given in the AFM |
| */ |
| public void overridePrimaryEncoding(SingleByteEncoding encoding) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Overriding primary encoding of " + getFontName() + " with: " + encoding); |
| } |
| AFMCharMetrics[] mapped = new AFMCharMetrics[256]; |
| for (AFMCharMetrics cm : this.charMetrics) { |
| NamedCharacter nc = cm.getCharacter(); |
| if (nc.hasSingleUnicodeValue()) { |
| int codePoint = encoding.mapChar(nc.getSingleUnicodeValue()); |
| if (codePoint > 0) { |
| if (mapped[codePoint] != null) { |
| if (LOG.isDebugEnabled()) { |
| AFMCharMetrics other = mapped[codePoint]; |
| String msg = "Not mapping character " + nc + " to code point " |
| + codePoint + " (" + Integer.toHexString(codePoint) + ") in " |
| + encoding + ". " |
| + other + " has already been assigned that code point."; |
| if (other.getUnicodeSequence() |
| .equals(nc.getUnicodeSequence())) { |
| msg += " This is a specialized glyph for the" |
| + " same Unicode character."; |
| //TODO should these be mapped to a private Unicode area to make |
| //them accessible? |
| } else { |
| msg += " This is a similar character."; |
| } |
| if (cm.getWidthX() != other.getWidthX()) { |
| msg += " They have differing widths: " |
| + cm.getWidthX() + " vs. " + other.getWidthX(); |
| } |
| LOG.debug(msg); |
| } |
| } else { |
| cm.setCharCode(codePoint); |
| mapped[codePoint] = cm; |
| } |
| } else { |
| cm.setCharCode(-1); |
| } |
| } else { |
| //No Unicode equivalent |
| cm.setCharCode(-1); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return "AFM: " + getFullName(); |
| } |
| |
| } |