You are viewing a plain text version of this content. The canonical link for it is here.
Posted to fop-commits@xmlgraphics.apache.org by fo...@apache.org on 2001/03/23 09:41:34 UTC

cvs commit: xml-fop/src/org/apache/fop/fonts TTFSubSetFile.java

fotis       01/03/23 00:41:34

  Added:       src/org/apache/fop/fonts TTFSubSetFile.java
  Log:
  subset fonts embedding [submitted by Tore Engvig]
  
  Revision  Changes    Path
  1.1                  xml-fop/src/org/apache/fop/fonts/TTFSubSetFile.java
  
  Index: TTFSubSetFile.java
  ===================================================================
  /* -- $Id: TTFSubSetFile.java,v 1.1 2001/03/23 08:41:34 fotis Exp $
   *
   * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
   * For details on use and redistribution please refer to the
   * LICENSE file included with these sources."
   */
  
  package org.apache.fop.fonts;
  import java.io.*;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.Vector;
  
  /**
   * Reads a TrueType file and generates a subset
   * That can be used to embed a TrueType CID font
   * TrueType tables needed for embedded CID fonts are:
   * "head", "hhea", "loca", "maxp", "cvt ", "prep", "glyf", "hmtx" and "fpgm"
   * The TrueType spec can be found at the Microsoft
   * Typography site: http://www.microsoft.com/truetype/
   */
  public class TTFSubSetFile extends TTFFile {
     byte[] output = null;
     int realSize = 0;
     int currentPos = 0;
  
         /* Offsets in name table to be filled out by table.
            The offsets are to the checkSum field */
     int cvtDirOffset = 0;
     int fpgmDirOffset = 0;
     int glyfDirOffset = 0;
     int headDirOffset = 0;
     int hheaDirOffset = 0;
     int hmtxDirOffset = 0;
     int locaDirOffset = 0;
     int maxpDirOffset = 0;
     int prepDirOffset = 0;
  
     int checkSumAdjustmentOffset = 0;
     int locaOffset = 0;
  
         /**
          * Initalize the output array
          */
     private void init(int size) {
        output = new byte[size];
        realSize = 0;
        currentPos = 0;
  
            // createDirectory()
     }
  
         /**
            Create the directory table
          */
     private void createDirectory() {
        int numTables = 9;
            // Create the TrueType header
        writeByte((byte)0);
        writeByte((byte)1);
        writeByte((byte)0);
        writeByte((byte)0);
        realSize+=4;
  
        writeUShort(numTables);
        realSize += 2;
  
            // Create searchRange, entrySelector and rangeShift
        int maxPow = maxPow2(numTables);
        int searchRange = maxPow*16;
        writeUShort(searchRange);
        realSize += 2;
  
        writeUShort(maxPow);
        realSize += 2;
  
        writeUShort((numTables*16) - searchRange);
        realSize += 2;
  
            // Create space for the table entries
        writeString("cvt ");
        cvtDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("fpgm");
        fpgmDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("glyf");
        glyfDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("head");
        headDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("hhea");
        hheaDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("hmtx");
        hmtxDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("loca");
        locaDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("maxp");
        maxpDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
  
        writeString("prep");
        prepDirOffset = currentPos;
        currentPos+=12;
        realSize+=16;
     }
  
  
         /**
          * Copy the cvt table as is from original font to subset font
          */
     private void createCvt(FontFileReader in) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt ");
        if (entry != null) {
           pad4();
           seek_tab(in, "cvt ", 0);
           System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
                            0, output, currentPos, (int)entry.length);
  
           int checksum = getCheckSum(currentPos, (int)entry.length);
           writeULong(cvtDirOffset, checksum);
           writeULong(cvtDirOffset+4, currentPos);
           writeULong(cvtDirOffset+8, (int)entry.length);
           currentPos+=(int)entry.length;
           realSize+=(int)entry.length;
        } else {
           throw new IOException ("Can't find cvt table");
        }
     }
  
  
  
         /**
          * Copy the fpgm table as is from original font to subset font
          */
     private void createFpgm(FontFileReader in) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm");
        if (entry != null) {
           pad4();
           seek_tab(in, "fpgm", 0);
           System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
                            0, output, currentPos, (int)entry.length);
           int checksum = getCheckSum(currentPos, (int)entry.length);
           writeULong(fpgmDirOffset, checksum);
           writeULong(fpgmDirOffset+4, currentPos);
           writeULong(fpgmDirOffset+8, (int)entry.length);
           currentPos+=(int)entry.length;
           realSize+=(int)entry.length;
        } else {
           throw new IOException ("Can't find fpgm table");
        }
     }
  
  
  
         /**
          * Create an empty loca table without updating checksum
          */
     private void createLoca(int size) throws IOException {
        pad4();
        locaOffset = currentPos;
        writeULong(locaDirOffset+4, currentPos);
        writeULong(locaDirOffset+8, size*4+4);
        currentPos+=size*4+4;
        realSize+=size*4+4;
     }
  
  
         /**
          * Copy the maxp table as is from original font to subset font
          * and set num glyphs to size
          */
     private void createMaxp(FontFileReader in, int size) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp");
        if (entry != null) {
           pad4();
           seek_tab(in, "maxp", 0);
           System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
                            0, output, currentPos, (int)entry.length);
           writeUShort(currentPos+4, size);
  
           int checksum = getCheckSum(currentPos, (int)entry.length);
           writeULong(maxpDirOffset, checksum);
           writeULong(maxpDirOffset+4, currentPos);
           writeULong(maxpDirOffset+8, (int)entry.length);
           currentPos+=(int)entry.length;
           realSize+=(int)entry.length;
        } else {
           throw new IOException ("Can't find maxp table");
        }
     }
  
  
         /**
          * Copy the prep table as is from original font to subset font
          */
     private void createPrep(FontFileReader in) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep");
        if (entry != null) {
           pad4();
           seek_tab(in, "prep", 0);
           System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
                            0, output, currentPos, (int)entry.length);
  
           int checksum = getCheckSum(currentPos, (int)entry.length);
           writeULong(prepDirOffset, checksum);
           writeULong(prepDirOffset+4, currentPos);
           writeULong(prepDirOffset+8, (int)entry.length);
           currentPos+=(int)entry.length;
           realSize+=(int)entry.length;
        } else {
           throw new IOException ("Can't find prep table");
        }
     }
  
  
         /**
          * Copy the hhea table as is from original font to subset font
          * and fill in size of hmtx table
          */
     private void createHhea(FontFileReader in, int size) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea");
        if (entry != null) {
           pad4();
           seek_tab(in, "hhea", 0);
           System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
                            0, output, currentPos, (int)entry.length);
           writeUShort((int)entry.length + currentPos-2, size);
  
           int checksum = getCheckSum(currentPos, (int)entry.length);
           writeULong(hheaDirOffset, checksum);
           writeULong(hheaDirOffset+4, currentPos);
           writeULong(hheaDirOffset+8, (int)entry.length);
           currentPos+=(int)entry.length;
           realSize+=(int)entry.length;
        } else {
           throw new IOException ("Can't find hhea table");
        }
     }
  
  
         /**
          * Copy the head table as is from original font to subset font
          * and set indexToLocaFormat to long and set
          * checkSumAdjustment to 0, store offset to checkSumAdjustment
          * in checkSumAdjustmentOffset
          */
     private void createHead(FontFileReader in) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head");
        if (entry != null) {
           pad4();
           seek_tab(in, "head", 0);
           System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
                            0, output, currentPos, (int)entry.length);
  
           checkSumAdjustmentOffset = currentPos + 8;
           output[currentPos+8] = 0; // Set checkSumAdjustment to 0
           output[currentPos+9] = 0;
           output[currentPos+10] = 0;
           output[currentPos+11] = 0;
           output[currentPos+(int)entry.length-2] = 0; // long locaformat
  
           int checksum = getCheckSum(currentPos, (int)entry.length);
           writeULong(headDirOffset, checksum);
           writeULong(headDirOffset+4, currentPos);
           writeULong(headDirOffset+8, (int)entry.length);
  
           currentPos+=(int)entry.length;
           realSize+=(int)entry.length;
        } else {
           throw new IOException ("Can't find head table");
        }
     }
  
  
         /**
          * Create the glyf table and fill in loca table
          */
     private void createGlyf(FontFileReader in, Hashtable glyphs)
        throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
        int size = 0;
        int start = 0;
        int endOffset = 0; // Store this as the last loca
        if (entry != null) {
           pad4();
           start = currentPos;
  
           for (Enumeration e = glyphs.keys(); e.hasMoreElements();) {
              int glyphLength = 0;
              Integer origIndex = (Integer)e.nextElement();
              Integer subsetIndex = (Integer)glyphs.get(origIndex);
  
              int nextOffset = 0;
              if (origIndex.intValue() >= (mtx_tab.length-1))
                 nextOffset = (int)lastLoca;
              else
                 nextOffset = (int)mtx_tab[origIndex.intValue()+1].offset;
  
              glyphLength = nextOffset -
                 (int)mtx_tab[origIndex.intValue()].offset;
  
                  // Copy glyph
              System.arraycopy(in.getBytes((int)entry.offset +
                                           (int)mtx_tab[origIndex.intValue()].offset,
                                           glyphLength),
                               0, output,
                               currentPos,
                               glyphLength);
  
  
                  // Update loca table
              writeULong(locaOffset + subsetIndex.intValue()*4,
                         currentPos-start);
              if ((currentPos - start + glyphLength) > endOffset)
                  endOffset = (currentPos - start + glyphLength);
  
              currentPos+=glyphLength;
              realSize+=glyphLength;
  
           }
  
           size = currentPos - start;
  
           int checksum = getCheckSum(currentPos, size);
           writeULong(glyfDirOffset, checksum);
           writeULong(glyfDirOffset+4, start);
           writeULong(glyfDirOffset+8, size);
           currentPos+=12;
           realSize+=12;
  
               // Update loca checksum and last loca index
           writeULong(locaOffset + glyphs.size()*4,
                      endOffset);
  
           checksum = getCheckSum(locaOffset, glyphs.size()*4+4);
           writeULong(locaDirOffset, checksum);
        } else {
           throw new IOException ("Can't find glyf table");
        }
     }
  
  
         /**
          * Create the hmtx table by copying metrics from original
          * font to subset font. The glyphs hashtable contains an
          * Integer key and Integer value that maps the original
          * metric (key) to the subset metric (value)
          */
     private void createHmtx(FontFileReader in, Hashtable glyphs)
        throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx");
  
        int longHorMetricSize = glyphs.size()*2;
        int leftSideBearingSize = glyphs.size()*2;
        int hmtxSize = longHorMetricSize + leftSideBearingSize;
  
        if (entry != null) {
           pad4();
           int offset = (int)entry.offset;
           for (Enumeration e = glyphs.keys(); e.hasMoreElements();) {
              Integer origIndex = (Integer)e.nextElement();
              Integer subsetIndex = (Integer)glyphs.get(origIndex);
  
              writeUShort(currentPos+subsetIndex.intValue()*4,
                          mtx_tab[origIndex.intValue()].wx);
              writeUShort(currentPos+subsetIndex.intValue()*4+2,
                          mtx_tab[origIndex.intValue()].lsb);
           }
  
           int checksum = getCheckSum(currentPos, hmtxSize);
           writeULong(hmtxDirOffset, checksum);
           writeULong(hmtxDirOffset+4, currentPos);
           writeULong(hmtxDirOffset+8, hmtxSize);
           currentPos+=hmtxSize;
           realSize+=hmtxSize;
        } else {
           throw new IOException ("Can't find hmtx table");
        }
     }
  
          /**
           * Returns a Vector containing the glyph itself plus all glyphs
           * that this composite glyph uses
           */
      private Vector getIncludedGlyphs(FontFileReader in,
                                       int glyphOffset, Integer glyphIdx)
          throws IOException {
          Vector ret = new Vector();
          ret.addElement(glyphIdx);
          int offset = glyphOffset +
              (int)mtx_tab[glyphIdx.intValue()].offset + 10;
          Integer compositeIdx = null;
          int flags = 0;
          boolean moreComposites = true;
          while (moreComposites) {
              flags = in.readTTFUShort(offset);
              compositeIdx = new Integer(in.readTTFUShort(offset+2));
              ret.addElement(compositeIdx);
  
              offset+=4;
              if ((flags & 1) > 0) {
                      // ARG_1_AND_ARG_2_ARE_WORDS
                  offset+=4;
              } else {
                  offset+=2;
              }
  
              if ((flags & 8) > 0)
                  offset+=2; // WE_HAVE_A_SCALE
              else if ((flags & 64) > 0)
                  offset+=4; // WE_HAVE_AN_X_AND_Y_SCALE
              else if ((flags & 128) > 0)
                  offset+=8; // WE_HAVE_A_TWO_BY_TWO
  
              if ((flags & 32) > 0)
                  moreComposites = true;
              else
                  moreComposites = false;
          }
  
          return ret;
      }
  
  
          /**
           * Rewrite all compositepointers in glyphindex glyphIdx
           *
           */
      private void remapComposite(FontFileReader in, Hashtable glyphs,
                                  int glyphOffset, Integer glyphIdx)
          throws IOException {
          int offset = glyphOffset +
              (int)mtx_tab[glyphIdx.intValue()].offset + 10;
  
          Integer compositeIdx = null;
          int flags = 0;
          boolean moreComposites = true;
  
          while (moreComposites) {
              flags = in.readTTFUShort(offset);
              compositeIdx = new Integer(in.readTTFUShort(offset+2));
              Integer newIdx = (Integer)glyphs.get(compositeIdx);
              in.writeTTFUShort(offset+2, newIdx.intValue());
  
              offset+=4;
  
              if ((flags & 1) > 0) {
                      // ARG_1_AND_ARG_2_ARE_WORDS
                  offset+=4;
              } else {
                  offset+=2;
              }
  
              if ((flags & 8) > 0 ) {
                  offset+=2; // WE_HAVE_A_SCALE
              } else if ((flags & 64) > 0) {
                  offset+=4; // WE_HAVE_AN_X_AND_Y_SCALE
              } else if ((flags & 128) > 0) {
                  offset+=8; // WE_HAVE_A_TWO_BY_TWO
              }
  
              if ((flags & 32) > 0)
                  moreComposites = true;
              else
                  moreComposites = false;
          }
      }
  
  
         /**
          * Scan all the original glyphs for composite glyphs and add those glyphs
          * to the glyphmapping also rewrite the composite glyph pointers to the new
          * mapping
          */
     private void scanGlyphs(FontFileReader in, Hashtable glyphs) throws IOException {
        TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
        Hashtable newComposites = null;
        Hashtable allComposites = new Hashtable();
  
        int newIndex = glyphs.size();
  
        if (entry != null) {
           while (newComposites == null || newComposites.size() > 0) {
                    // Inefficient to iterate through all glyphs
              newComposites = new Hashtable();
  
              for (Enumeration e = glyphs.keys(); e.hasMoreElements();) {
                  Integer origIndex = (Integer)e.nextElement();
  
                  if (in.readTTFShort(entry.offset +
                                      mtx_tab[origIndex.intValue()].offset) < 0) {
                          // origIndex is a composite glyph
                      allComposites.put(origIndex, glyphs.get(origIndex));
                      Vector composites = getIncludedGlyphs(in, (int)entry.offset,
                                                            origIndex);
  
                          // Iterate through all composites pointed to
                          // by this composite and check if they exists
                          // in the glyphs map, add them if not.
                      for (Enumeration cps=composites.elements();
                           cps.hasMoreElements();) {
  
                          Integer cIdx =(Integer)cps.nextElement();
                          if (glyphs.get(cIdx) == null &&
                              newComposites.get(cIdx) == null) {
                              newComposites.put(cIdx, new Integer(newIndex));
                              newIndex++;
                          }
                      }
                  }
              }
  
                  // Add composites to glyphs
              for (Enumeration m = newComposites.keys(); m.hasMoreElements();) {
                  Integer im = (Integer)m.nextElement();
                  glyphs.put(im, newComposites.get(im));
              }
           }
  
               // Iterate through all composites to remap their composite index
  
           for (Enumeration ce = allComposites.keys(); ce.hasMoreElements();) {
                   remapComposite(in, glyphs, (int)entry.offset,
                                  (Integer)ce.nextElement());
           }
  
        } else {
           throw new IOException ("Can't find glyf table");
        }
     }
  
  
  
         /**
          * glyphs has old index as (Integer) key and new index
          * as (Integer) value
          */
  
     public byte[] readFont(FontFileReader in, String name,
                          Hashtable glyphs) throws IOException {
  
              /* Check if TrueType collection, and that the name
                 exists in the collection
              */
          if (!checkTTC(in, name, false))
              throw new IOException("Failed to read font");
  
          output = new byte[in.getFileSize()];
  
          readDirTabs(in);
          readFontHeader(in);
          getNumGlyphs(in);
          readHorizontalHeader(in);
          readHorizontalMetrics(in);
          readIndexToLocation(in);
  
          scanGlyphs(in, glyphs);
  
          createDirectory(); // Create the TrueType header and directory
          createCvt(in); // copy the cvt table
          createFpgm(in); // copy fpgm table
          createHead(in);
          createHhea(in, glyphs.size()); // Create the hhea table
          createHmtx(in, glyphs); // Create hmtx table
          createMaxp(in, glyphs.size()); // copy the maxp table
          createPrep(in); // copy prep table
          createLoca(glyphs.size()); // create empty loca table
          createGlyf(in, glyphs);
  
          pad4();
          createCheckSumAdjustment();
  
          byte[] ret = new byte[realSize];
          System.arraycopy(output, 0, ret, 0, realSize);
  
          return ret;
     }
  
         /**
          * writes a ISO-8859-1 string at the currentPosition
          * updates currentPosition but not realSize
          * @return number of bytes written
          */
     private int writeString(String str) {
        int length = 0;
        try {
           byte[] buf = str.getBytes("ISO-8859-1");
           System.arraycopy(buf, 0, output, currentPos, buf.length);
           length = buf.length;
           currentPos += length;
        } catch (Exception e) {
               // This should never happen!
        }
  
        return length;
     }
  
         /**
            Appends a byte to the output array,
            updates currentPost but not realSize
         */
     private void writeByte(byte b) {
        output[currentPos++] = b;
     }
  
         /**
            Appends a USHORT to the output array,
            updates currentPost but not realSize
         */
     private void writeUShort(int s) {
        byte b1 = (byte)((s >> 8) & 0xff);
        byte b2 = (byte) (s & 0xff);
        writeByte(b1);
        writeByte(b2);
     }
  
         /**
            Appends a USHORT to the output array,
            at the given position without changing currentPos
         */
     private void writeUShort(int pos, int s) {
        byte b1 = (byte)((s >> 8) & 0xff);
        byte b2 = (byte) (s & 0xff);
        output[pos] = b1;
        output[pos+1] = b2;
     }
  
         /**
            Appends a ULONG to the output array,
            updates currentPos but not realSize
         */
     private void writeULong(int s) {
        byte b1 = (byte)((s >> 24) & 0xff);
        byte b2 = (byte)((s >> 16) & 0xff);
        byte b3 = (byte)((s >> 8) & 0xff);
        byte b4 = (byte) (s & 0xff);
        writeByte(b1);
        writeByte(b2);
        writeByte(b3);
        writeByte(b4);
     }
  
         /**
            Appends a ULONG to the output array,
            at the given position without changing currentPos
         */
     private void writeULong(int pos, int s) {
        byte b1 = (byte)((s >> 24) & 0xff);
        byte b2 = (byte)((s >> 16) & 0xff);
        byte b3 = (byte)((s >> 8) & 0xff);
        byte b4 = (byte) (s & 0xff);
        output[pos] = b1;
        output[pos+1] = b2;
        output[pos+2] = b3;
        output[pos+3] = b4;
     }
  
         /** Read a signed short value at given position
          */
     private short readShort(int pos) {
        int ret = readUShort(pos);
        return (short)ret;
     }
  
         /** Read a unsigned short value at given position
          */
     private int readUShort(int pos) {
        int ret = (int)output[pos];
        if (ret < 0)
           ret+=256;
        ret = ret << 8;
        if ((int)output[pos+1] < 0) {
           ret |= (int)output[pos+1]+256;
        } else
           ret |= (int)output[pos+1];
  
        return ret;
     }
  
         /**
          * Create a padding in the fontfile to align
          * on a 4-byte boundary
          */
     private void pad4() {
        int padSize = currentPos % 4;
        for (int i = 0; i < padSize; i++) {
           output[currentPos++] = 0;
           realSize++;
        }
     }
         /**
          * Returns the maximum power of 2 <= max
          */
     private int maxPow2(int max) {
        int i=0;
        while (Math.pow(2, (double)i) < max)
           i++;
  
        return (i-1);
     }
  
     private int log2(int num) {
        return (int)(Math.log((double)num)/Math.log(2));
     }
  
  
     private int getCheckSum(int start, int size) {
         return (int)getLongCheckSum(start, size);
     }
  
     private long getLongCheckSum(int start, int size) {
            // All the tables here are aligned on four byte boundaries
            // Add remainder to size if it's not a multiple of 4
        int remainder = size % 4;
        if (remainder != 0)
           size += remainder;
  
        long sum = 0;
  
        for (int i = 0; i < size; i+=4) {
           int l = (int)(output[start+i] << 24);
           l += (int)(output[start+i+1] << 16);
           l += (int)(output[start+i+2] << 16);
           l += (int)(output[start+i+3] << 16);
           sum += l;
           if (sum > 0xffffffff)
               sum = sum - 0xffffffff;
        }
  
        return sum;
     }
  
     private void createCheckSumAdjustment() {
        long sum = getLongCheckSum(0, realSize);
        int checksum = (int)(0xb1b0afba - sum);
        writeULong(checkSumAdjustmentOffset, checksum);
     }
  }
  
  
  
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: fop-cvs-help@xml.apache.org