aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/render/afp/fonts/AFPFontReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache/fop/render/afp/fonts/AFPFontReader.java')
-rw-r--r--src/java/org/apache/fop/render/afp/fonts/AFPFontReader.java608
1 files changed, 608 insertions, 0 deletions
diff --git a/src/java/org/apache/fop/render/afp/fonts/AFPFontReader.java b/src/java/org/apache/fop/render/afp/fonts/AFPFontReader.java
new file mode 100644
index 000000000..cb8718d8f
--- /dev/null
+++ b/src/java/org/apache/fop/render/afp/fonts/AFPFontReader.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.render.afp.fonts;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fop.render.afp.exceptions.FontRuntimeException;
+import org.apache.fop.render.afp.modca.AFPConstants;
+import org.apache.fop.render.afp.tools.StructuredFieldReader;
+
+/**
+ * The AFPFontReader is responsible for reading the font attributes from binary
+ * code page files and the character set metric files. In IBM font structure, a
+ * code page maps each character of text to the characters in a character set.
+ * Each character is translated into a code point. When the character is
+ * printed, each code point is matched to a character ID on the code page
+ * specified. The character ID is then matched to the image (raster pattern or
+ * outline pattern) of the character in the character set specified. The image
+ * in the character set is the image that is printed in the document. To be a
+ * valid code page for a particular character set, all character IDs in the code
+ * page must be included in that character set. <p/>This class will read the
+ * font information from the binary code page files and character set metric
+ * files in order to determine the correct metrics to use when rendering the
+ * formatted object. <p/>
+ *
+ * @author <a href="mailto:pete@townsend.uk.com">Pete Townsend </a>
+ */
+public final class AFPFontReader {
+
+ /**
+ * Static logging instance
+ */
+ protected static final Log log = LogFactory.getLog("org.apache.fop.render.afp.fonts");
+
+ /**
+ * Template used to convert lists to arrays.
+ */
+ private static final CharacterSetOrientation[] EMPTY_CSO_ARRAY = new CharacterSetOrientation[0];
+
+ /** Codepage MO:DCA structured field. */
+ private static final byte[] CODEPAGE_SF = new byte[] { (byte) 0xD3,
+ (byte) 0xA8, (byte) 0x87 };
+
+ /** Character table MO:DCA structured field. */
+ private static final byte[] CHARACTER_TABLE_SF = new byte[] { (byte) 0xD3,
+ (byte) 0x8C, (byte) 0x87 };
+
+ /** Font control MO:DCA structured field. */
+ private static final byte[] FONT_CONTROL_SF = new byte[] { (byte) 0xD3,
+ (byte) 0xA7, (byte) 0x89 };
+
+ /** Font orientation MO:DCA structured field. */
+ private static final byte[] FONT_ORIENTATION_SF = new byte[] { (byte) 0xD3,
+ (byte) 0xAE, (byte) 0x89 };
+
+ /** Font position MO:DCA structured field. */
+ private static final byte[] FONT_POSITION_SF = new byte[] { (byte) 0xD3,
+ (byte) 0xAC, (byte) 0x89 };
+
+ /** Font index MO:DCA structured field. */
+ private static final byte[] FONT_INDEX_SF = new byte[] { (byte) 0xD3,
+ (byte) 0x8C, (byte) 0x89 };
+
+ /**
+ * The conversion factor to millipoints for 240 dpi
+ */
+ private static final int FOP_100_DPI_FACTOR = 1;
+
+ /**
+ * The conversion factor to millipoints for 240 dpi
+ */
+ private static final int FOP_240_DPI_FACTOR = 300000;
+
+ /**
+ * The conversion factor to millipoints for 300 dpi
+ */
+ private static final int FOP_300_DPI_FACTOR = 240000;
+
+ /**
+ * The encoding to use to convert from EBCIDIC to ASCII
+ */
+ private static final String ASCII_ENCODING = "UTF8";
+
+ /**
+ * The collection of code pages
+ */
+ private static HashMap _codePages = new HashMap();
+
+ /**
+ * Load the font details and metrics into the CharacterSetMetric object,
+ * this will use the actual afp code page and character set files to load
+ * the object with the necessary metrics.
+ *
+ * @param characterSet the CharacterSetMetric object to populate
+ */
+ public static void loadCharacterSetMetric(CharacterSet characterSet) {
+
+ InputStream inputStream = null;
+
+ try {
+
+ /**
+ * Get the code page which contains the character mapping
+ * information to map the unicode character id to the graphic
+ * chracter global identifier.
+ */
+ String cp = new String(characterSet.getCodePage());
+ String path = characterSet.getPath();
+
+ HashMap codepage = (HashMap) _codePages.get(cp);
+
+ if (codepage == null) {
+ codepage = loadCodePage(cp, characterSet.getEncoding(), path);
+ _codePages.put(cp, codepage);
+ }
+
+ /**
+ * Load the character set metric information, no need to cache this
+ * information as it should be cached by the objects that wish to
+ * load character set metric information.
+ */
+ final String characterset = characterSet.getName();
+
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = AFPFontReader.class.getClassLoader();
+ }
+
+ URL url = classLoader.getResource(path);
+ if (url == null) {
+ try {
+ File file = new File(path);
+ url = file.toURL();
+ if (url == null) {
+ String msg = "CharacterSet file not found for "
+ + characterset + " in classpath: " + path;
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ }
+ } catch (MalformedURLException ex) {
+ String msg = "CharacterSet file not found for "
+ + characterset + " in classpath: " + path;
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ }
+
+ }
+
+ File directory = new File(url.getPath());
+
+ final String filterpattern = characterset.trim();
+ FilenameFilter filter = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.startsWith(filterpattern);
+ }
+ };
+
+ File[] csfont = directory.listFiles(filter);
+ if (csfont.length < 1) {
+ String msg = "CharacterSet file search for " + characterset
+ + " located " + csfont.length + " files";
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ } else if (csfont.length > 1) {
+ String msg = "CharacterSet file search for " + characterset
+ + " located " + csfont.length + " files";
+ log.warn(msg);
+ }
+
+ inputStream = inputStream = csfont[0].toURL().openStream();
+ if (inputStream == null) {
+ String msg = "Failed to open character set resource "
+ + characterset;
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ }
+
+ StructuredFieldReader sfr = new StructuredFieldReader(inputStream);
+
+ // Process D3A789 Font Control
+ FontControl fnc = processFontControl(sfr);
+
+ //process D3AE89 Font Orientation
+ CharacterSetOrientation[] csoArray = processFontOrientation(sfr);
+
+ //process D3AC89 Font Position
+ processFontPosition(sfr, csoArray, fnc.getDpi());
+
+ //process D38C89 Font Index (per orientation)
+ for (int i = 0; i < csoArray.length; i++) {
+ processFontIndex(sfr, csoArray[i], codepage, fnc.getDpi());
+ characterSet.addCharacterSetOrientation(csoArray[i]);
+ }
+
+ } catch (Exception ex) {
+ throw new FontRuntimeException(
+ "Failed to load the character set metrics for code page "
+ + characterSet.getCodePage(), ex);
+ } finally {
+ try {
+ inputStream.close();
+ } catch (Exception ex) {
+ // Ignore
+ }
+ }
+
+ }
+
+ /**
+ * Load the code page information from the appropriate file. The file name
+ * to load is determined by the code page name and the file extension 'CDP'.
+ *
+ * @param codePage
+ * the code page identifier
+ * @param encoding
+ * the encoding to use for the character decoding
+ */
+ private static HashMap loadCodePage(String codePage, String encoding,
+ String path) throws IOException, FileNotFoundException {
+
+ // Create the HashMap to store code page information
+ HashMap codepages = new HashMap();
+
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = AFPFontReader.class.getClassLoader();
+ }
+
+ URL url = classLoader.getResource(path);
+
+ if (url == null) {
+ try {
+ File file = new File(path);
+ url = file.toURL();
+ if (url == null) {
+ String msg = "CodePage file not found for " + codePage
+ + " in classpath: " + path;
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ }
+ } catch (MalformedURLException ex) {
+ String msg = "CodePage file not found for " + codePage
+ + " in classpath: " + path;
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ }
+
+ }
+
+ File directory = new File(url.getPath());
+
+ final String filterpattern = codePage.trim();
+ FilenameFilter filter = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.startsWith(filterpattern);
+ }
+ };
+
+ File[] codepage = directory.listFiles(filter);
+
+ if (codepage.length < 1) {
+ String msg = "CodePage file search for " + codePage + " located "
+ + codepage.length + " files";
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ } else if (codepage.length > 1) {
+ String msg = "CodePage file search for " + codePage + " located "
+ + codepage.length + " files";
+ log.warn(msg);
+ }
+
+ InputStream is = codepage[0].toURL().openStream();
+
+ if (is == null) {
+ String msg = "AFPFontReader:: loadCodePage(String):: code page file not found for "
+ + codePage;
+ log.error(msg);
+ throw new FileNotFoundException(msg);
+ }
+
+ StructuredFieldReader sfr = new StructuredFieldReader(is);
+ byte[] data = sfr.getNext(CHARACTER_TABLE_SF);
+
+ int position = 0;
+ byte[] gcgiBytes = new byte[8];
+ byte[] charBytes = new byte[1];
+
+ // Read data, ignoring bytes 0 - 2
+ for (int index = 3; index < data.length; index++) {
+ if (position < 8) {
+ // Build the graphic character global identifier key
+ gcgiBytes[position] = data[index];
+ position++;
+ } else if (position == 9) {
+ position = 0;
+ // Set the character
+ charBytes[0] = data[index];
+ String gcgiString = new String(gcgiBytes,
+ AFPConstants.EBCIDIC_ENCODING);
+ String charString = new String(charBytes, encoding);
+ int value = charString.charAt(0);
+ codepages.put(gcgiString, charString);
+ } else {
+ position++;
+ }
+ }
+
+ try {
+ is.close();
+ } catch (Exception ex) {
+ // Ignore
+ }
+
+ return codepages;
+
+ }
+
+ /**
+ * Process the font control details using the structured field reader.
+ *
+ * @param sfr
+ * the structured field reader
+ */
+ private static FontControl processFontControl(StructuredFieldReader sfr)
+ throws IOException {
+
+ byte[] fncData = sfr.getNext(FONT_CONTROL_SF);
+
+ int position = 0;
+
+ FontControl fontControl = new AFPFontReader().new FontControl();
+
+ if (fncData[7] == (byte) 0x02) {
+ fontControl.setRelative(true);
+ }
+
+ int dpi = (((fncData[9] & 0xFF) << 8) + (fncData[10] & 0xFF)) / 10;
+
+ fontControl.setDpi(dpi);
+
+ return fontControl;
+
+ }
+
+ /**
+ * Process the font orientation details from using the structured field
+ * reader.
+ *
+ * @param sfr
+ * the structured field reader
+ */
+ private static CharacterSetOrientation[] processFontOrientation(
+ StructuredFieldReader sfr) throws IOException {
+
+ byte[] data = sfr.getNext(FONT_ORIENTATION_SF);
+
+ int position = 0;
+ byte[] fnoData = new byte[26];
+
+ ArrayList orientations = new ArrayList();
+
+ // Read data, ignoring bytes 0 - 2
+ for (int index = 3; index < data.length; index++) {
+ // Build the font orientation record
+ fnoData[position] = data[index];
+ position++;
+
+ if (position == 26) {
+
+ position = 0;
+
+ int orientation = 0;
+
+ switch (fnoData[2]) {
+ case 0x00:
+ orientation = 0;
+ break;
+ case 0x2D:
+ orientation = 90;
+ break;
+ case 0x5A:
+ orientation = 180;
+ break;
+ case (byte) 0x87:
+ orientation = 270;
+ break;
+ default:
+ System.out.println("ERROR: Oriantation");
+ }
+
+ CharacterSetOrientation cso = new CharacterSetOrientation(
+ orientation);
+ orientations.add(cso);
+
+ }
+ }
+
+ return (CharacterSetOrientation[]) orientations
+ .toArray(EMPTY_CSO_ARRAY);
+ }
+
+ /**
+ * Populate the CharacterSetOrientation object in the suplied array with the
+ * font position details using the supplied structured field reader.
+ *
+ * @param sfr
+ * the structured field reader
+ * @param csoArray
+ * the array of CharacterSetOrientation objects
+ */
+ private static void processFontPosition(StructuredFieldReader sfr,
+ CharacterSetOrientation[] csoArray, int dpi) throws IOException {
+
+ byte[] data = sfr.getNext(FONT_POSITION_SF);
+
+ int position = 0;
+ byte[] fpData = new byte[26];
+
+ int csoIndex = 0;
+ int fopFactor = 0;
+
+ switch (dpi) {
+ case 100:
+ fopFactor = FOP_100_DPI_FACTOR;
+ break;
+ case 240:
+ fopFactor = FOP_240_DPI_FACTOR;
+ break;
+ case 300:
+ fopFactor = FOP_300_DPI_FACTOR;
+ break;
+ default:
+ String msg = "Unsupported font resolution of " + dpi + " dpi.";
+ log.error(msg);
+ throw new IOException(msg);
+ }
+
+ // Read data, ignoring bytes 0 - 2
+ for (int index = 3; index < data.length; index++) {
+ if (position < 22) {
+ // Build the font orientation record
+ fpData[position] = data[index];
+ } else if (position == 22) {
+
+ position = 0;
+
+ CharacterSetOrientation cso = csoArray[csoIndex];
+
+ int xHeight = ((fpData[2] & 0xFF) << 8) + (fpData[3] & 0xFF);
+ int capHeight = ((fpData[4] & 0xFF) << 8) + (fpData[5] & 0xFF);
+ int ascHeight = ((fpData[6] & 0xFF) << 8) + (fpData[7] & 0xFF);
+ int dscHeight = ((fpData[8] & 0xFF) << 8) + (fpData[9] & 0xFF);
+
+ dscHeight = dscHeight * -1;
+
+ cso.setXHeight(xHeight * fopFactor);
+ cso.setCapHeight(capHeight * fopFactor);
+ cso.setAscender(ascHeight * fopFactor);
+ cso.setDescender(dscHeight * fopFactor);
+
+ csoIndex++;
+
+ fpData[position] = data[index];
+
+ }
+
+ position++;
+ }
+
+ }
+
+ /**
+ * Process the font index details for the character set orientation.
+ *
+ * @param sfr
+ * the structured field reader
+ * @param cso
+ * the CharacterSetOrientation object to populate
+ * @param codepage
+ * the map of code pages
+ */
+ private static void processFontIndex(StructuredFieldReader sfr,
+ CharacterSetOrientation cso, HashMap codepage, int dpi)
+ throws IOException {
+
+ byte[] data = sfr.getNext(FONT_INDEX_SF);
+
+ int fopFactor = 0;
+
+ switch (dpi) {
+ case 100:
+ fopFactor = FOP_100_DPI_FACTOR;
+ break;
+ case 240:
+ fopFactor = FOP_240_DPI_FACTOR;
+ break;
+ case 300:
+ fopFactor = FOP_300_DPI_FACTOR;
+ break;
+ default:
+ String msg = "Unsupported font resolution of " + dpi + " dpi.";
+ log.error(msg);
+ throw new IOException(msg);
+ }
+
+ int position = 0;
+
+ byte[] gcgid = new byte[8];
+ byte[] fiData = new byte[20];
+
+ int lowest = 255;
+ int highest = 0;
+
+ // Read data, ignoring bytes 0 - 2
+ for (int index = 3; index < data.length; index++) {
+ if (position < 8) {
+ gcgid[position] = (byte) data[index];
+ position++;
+ } else if (position < 27) {
+ fiData[position - 8] = (byte) data[index];
+ position++;
+ } else if (position == 27) {
+
+ fiData[position - 8] = (byte) data[index];
+
+ position = 0;
+
+ String gcgiString = new String(gcgid, AFPConstants.EBCIDIC_ENCODING);
+
+ String idx = (String) codepage.get(gcgiString);
+
+ if (idx != null) {
+
+ int cidx = idx.charAt(0);
+ int width = ((fiData[0] & 0xFF) << 8) + (fiData[1] & 0xFF);
+
+ if (cidx < lowest) {
+ lowest = cidx;
+ }
+
+ if (cidx > highest) {
+ highest = cidx;
+ }
+
+ int a = (width * fopFactor);
+
+ cso.setWidth(cidx, a);
+
+ }
+
+ }
+ }
+
+ cso.setFirstChar(lowest);
+ cso.setLastChar(highest);
+
+ }
+
+ private class FontControl {
+
+ private int _dpi;
+
+ private boolean isRelative = false;
+
+ public int getDpi() {
+ return _dpi;
+ }
+
+ public void setDpi(int i) {
+ _dpi = i;
+ }
+
+ public boolean isRelative() {
+ return isRelative;
+ }
+
+ public void setRelative(boolean b) {
+ isRelative = b;
+ }
+ }
+
+} \ No newline at end of file