All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lowagie.text.pdf.CFFFontSubset Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
/*
 * $Id: CFFFontSubset.java 3573 2008-07-21 15:08:04Z blowagie $
 *
 * Copyright 2004 Oren Manor and Ygal Blum
 *
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the License.
 *
 * The Original Code is 'iText, a free JAVA-PDF library'.
 *
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 * the Initial Developer are Copyright (C) 1999-2005 by Bruno Lowagie.
 * All Rights Reserved.
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 * are Copyright (C) 2000-2005 by Paulo Soares. All Rights Reserved.
 *
 * Contributor(s): all the names of the contributors are added in the source code
 * where applicable.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
 * provisions of LGPL are applicable instead of those above.  If you wish to
 * allow use of your version of this file only under the terms of the LGPL
 * License and not to allow others to use your version of this file under
 * the MPL, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the LGPL.
 * If you do not delete the provisions above, a recipient may use your version
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the MPL as stated above or under the terms of the GNU
 * Library General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
 * details.
 *
 * If you didn't download this code from the following link, you should check if
 * you aren't using an obsolete version:
 * https://github.com/LibrePDF/OpenPDF
 */
package com.lowagie.text.pdf;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * This Class subsets a CFF Type Font. The subset is preformed for CID fonts and NON CID fonts.
 * The Charstring is subsetted for both types. For CID fonts only the FDArray which are used are embedded. 
 * The Lsubroutines of the FDArrays used are subsetted as well. The Subroutine subset supports both Type1 and Type2
 * formatting although only tested on Type2 Format.
 * For Non CID the Lsubroutines are subsetted. On both types the Gsubroutines is subsetted. 
 * A font which was not of CID type is transformed into CID as a part of the subset process. 
 * The CID synthetic creation was written by Sivan Toledo ([email protected]) 
 * @author Oren Manor ([email protected]) and Ygal Blum ([email protected])
 */
public class CFFFontSubset extends CFFFont {

    /**
     * The Strings in this array represent Type1/Type2 operator names
     */
    static final String[] SubrsFunctions = {
            "RESERVED_0", "hstem", "RESERVED_2", "vstem", "vmoveto", "rlineto", "hlineto", "vlineto",
            "rrcurveto", "RESERVED_9", "callsubr", "return", "escape", "RESERVED_13",
            "endchar", "RESERVED_15", "RESERVED_16", "RESERVED_17", "hstemhm", "hintmask",
            "cntrmask", "rmoveto", "hmoveto", "vstemhm", "rcurveline", "rlinecurve", "vvcurveto",
            "hhcurveto", "shortint", "callgsubr", "vhcurveto", "hvcurveto"
    };
    /**
     * The Strings in this array represent Type1/Type2 escape operator names
     */
    static final String[] SubrsEscapeFuncs = {
            "RESERVED_0", "RESERVED_1", "RESERVED_2", "and", "or", "not", "RESERVED_6",
            "RESERVED_7", "RESERVED_8", "abs", "add", "sub", "div", "RESERVED_13", "neg",
            "eq", "RESERVED_16", "RESERVED_17", "drop", "RESERVED_19", "put", "get", "ifelse",
            "random", "mul", "RESERVED_25", "sqrt", "dup", "exch", "index", "roll", "RESERVED_31",
            "RESERVED_32", "RESERVED_33", "hflex", "flex", "hflex1", "flex1", "RESERVED_REST"
    };
    
    /**
    *  Operator codes for unused  CharStrings and unused local and global Subrs
    */
    static final byte ENDCHAR_OP = 14;
    static final byte RETURN_OP = 11;
    
    /**
     * A HashMap containing the glyphs used in the text after being converted
     * to glyph number by the CMap 
     */
    HashMap GlyphsUsed;
    /**
     * The GlyphsUsed keys as an ArrayList
     */
    ArrayList glyphsInList;
    /**
     * A HashMap for keeping the FDArrays being used by the font
     */
    HashMap FDArrayUsed = new HashMap<>();
    /**
     * A HashMaps array for keeping the subroutines used in each FontDict
     */
    HashMap[] hSubrsUsed;
    /**
     * The SubroutinesUsed HashMaps as ArrayLists
     */
    ArrayList[] lSubrsUsed;
    /**
     * A HashMap for keeping the Global subroutines used in the font
     */
    HashMap hGSubrsUsed  = new HashMap<>();
    /**
     * The Global SubroutinesUsed HashMaps as ArrayLists
     */
    ArrayList lGSubrsUsed = new ArrayList<>();
    /**
     * A HashMap for keeping the subroutines used in a non-cid font
     */
    HashMap hSubrsUsedNonCID  = new HashMap<>();
    /**
     * The SubroutinesUsed HashMap as ArrayList
     */
    ArrayList lSubrsUsedNonCID = new ArrayList<>();
    /**
     * An array of the new Indexes for the local Subr. One index for each FontDict
     */
    byte[][] NewLSubrsIndex;
    /**
     * The new subroutines index for a non-cid font
     */
    byte[] NewSubrsIndexNonCID;
    /**
     * The new global subroutines index of the font
     */
    byte[] NewGSubrsIndex;
    /**
     * The new CharString of the font
     */
    byte[] NewCharStringsIndex;
    
    /**
     * The bias for the global subroutines
     */
    int GBias = 0;
    
    /**
     * The linked list for generating the new font stream
     */
    LinkedList OutputList;
    
    /**
     * Number of arguments to the stem operators in a subroutine calculated recursively
     */
    int NumOfHints=0;

    
    /**     
     * C'tor for CFFFontSubset
     * @param rf - The font file
     * @param GlyphsUsed - a HashMap that contains the glyph used in the subset 
     */
    public CFFFontSubset(RandomAccessFileOrArray rf, HashMap GlyphsUsed){
        // Use CFFFont c'tor in order to parse the font file.
        super(rf);
        this.GlyphsUsed = GlyphsUsed;
        //Put the glyphs into a list
        glyphsInList = new ArrayList<>(GlyphsUsed.keySet());
        
        
        for (int i=0;i=0)
            {
                // Process the FDSelect
                readFDSelect(i);
                // Build the FDArrayUsed hashmap
                BuildFDArrayUsed(i);
            }
            if (fonts[i].isCID)
                // Build the FD Array used Hash Map
                ReadFDArray(i);
            // compute the charset length 
            fonts[i].CharsetLength = CountCharset(fonts[i].charsetOffset,fonts[i].nglyphs);
        }
    }

    /**
     * Calculates the length of the charset according to its format
     * @param Offset The Charset Offset
     * @param NumofGlyphs Number of glyphs in the font
     * @return the length of the Charset
     */
    int CountCharset(int Offset,int NumofGlyphs){
        int format;
        int Length=0;
        seek(Offset);
        // Read the format
        format = getCard8();
        // Calc according to format
        switch (format){
            case 0:
                Length = 1+2*NumofGlyphs;
                break;
            case 1:
                Length = 1+3*CountRange(NumofGlyphs,1);
                break;
            case 2:
                Length = 1+4*CountRange(NumofGlyphs,2);
                break;
            default:
                break;
        }
        return Length;
    }
    
    /**
     * Function calculates the number of ranges in the Charset
     * @param NumofGlyphs The number of glyphs in the font
     * @param Type The format of the Charset
     * @return The number of ranges in the Charset data structure
     */
    int CountRange(int NumofGlyphs,int Type){
        int num=0;
        char Sid;
        int i=1,nLeft;
        while (i= 0)
                GBias = CalcBias(gsubrIndexOffset,j);

            // Prepare the new CharStrings Index
            BuildNewCharString(j);
             // Prepare the new Global and Local Subrs Indices
            BuildNewLGSubrs(j);
            // Build the new file 
            byte[] Ret = BuildNewFile(j);
            return Ret;
        }
        finally {
            try {
                buf.close();
            }
            catch (Exception e) {
                // empty on purpose
            }
        }
    }

    /**
     * Function calcs bias according to the CharString type and the count
     * of the subrs
     * @param Offset The offset to the relevant subrs index
     * @param Font the font
     * @return The calculated Bias
     */
    protected int CalcBias(int Offset,int Font)
    {
        seek(Offset);
        int nSubrs = getCard16();
        // If type==1 -> bias=0 
        if (fonts[Font].CharstringType == 1)
            return 0;
        // else calc according to the count
        else if (nSubrs < 1240)
            return 107;
        else if (nSubrs < 33900)
            return 1131;
        else
            return 32768;
    }

    /**
     *Function uses BuildNewIndex to create the new index of the subset charstrings
     * @param FontIndex the font
     * @throws IOException on error
     */
    protected void BuildNewCharString(int FontIndex) throws IOException 
    {
        NewCharStringsIndex = BuildNewIndex(fonts[FontIndex].charstringsOffsets,GlyphsUsed,ENDCHAR_OP);
    }
    
    /**
     * Function builds the new local and global subsrs indices. IF CID then All of
     * the FD Array lsubrs will be subsetted. 
     * @param Font the font
     * @throws IOException on error
     */
    @SuppressWarnings("unchecked")
    protected void BuildNewLGSubrs(int Font)throws IOException
    {
        // If the font is CID then the lsubrs are divided into FontDicts.
        // for each FD array the lsubrs will be subsetted.
        if(fonts[Font].isCID)
        {
            // Init the hashmap-array and the arraylist-array to hold the subrs used
            // in each private dict.
            HashMap mapClazz = new HashMap<>();
            hSubrsUsed = (HashMap[]) Array.newInstance(mapClazz.getClass(), fonts[Font].fdprivateOffsets.length);
            ArrayList listClass = new ArrayList<>();
            lSubrsUsed = (ArrayList[]) Array.newInstance(listClass.getClass(), fonts[Font].fdprivateOffsets.length);
            // A [][] which will store the byte array for each new FD Array lsubs index
            NewLSubrsIndex = new byte[fonts[Font].fdprivateOffsets.length][];
            // An array to hold the offset for each Lsubr index 
            fonts[Font].PrivateSubrsOffset = new int[fonts[Font].fdprivateOffsets.length];
            // A [][] which will store the offset array for each lsubr index            
            fonts[Font].PrivateSubrsOffsetsArray = new int[fonts[Font].fdprivateOffsets.length][];
            
            // Put the FDarrayUsed into a list
            List FDInList = new ArrayList<>(FDArrayUsed.keySet());
            // For each FD array which is used subset the lsubr 
            for (int FD : FDInList) {
                // The FDArray index, Hash Map, Array List to work on
                hSubrsUsed[FD] = new HashMap<>();
                lSubrsUsed[FD] = new ArrayList<>();
                //Reads the private dicts looking for the subr operator and 
                // store both the offset for the index and its offset array
                BuildFDSubrsOffsets(Font, FD);
                // Verify that FDPrivate has a LSubrs index
                if (fonts[Font].PrivateSubrsOffset[FD] >= 0) {
                    //Scans the Charstring data storing the used Local and Global subroutines 
                    // by the glyphs. Scans the Subrs recursively. 
                    BuildSubrUsed(Font, FD, fonts[Font].PrivateSubrsOffset[FD], fonts[Font].PrivateSubrsOffsetsArray[FD], hSubrsUsed[FD], lSubrsUsed[FD]);
                    // Builds the New Local Subrs index
                    NewLSubrsIndex[FD] = BuildNewIndex(fonts[Font].PrivateSubrsOffsetsArray[FD], hSubrsUsed[FD], RETURN_OP);
                }
            }
        }
        // If the font is not CID && the Private Subr exists then subset:
        else if (fonts[Font].privateSubrs>=0)
        {
            // Build the subrs offsets;
            fonts[Font].SubrsOffsets = getIndex(fonts[Font].privateSubrs);
            //Scans the Charstring data storing the used Local and Global subroutines 
            // by the glyphs. Scans the Subrs recursively.
            BuildSubrUsed(Font,-1,fonts[Font].privateSubrs,fonts[Font].SubrsOffsets,hSubrsUsedNonCID,lSubrsUsedNonCID);
        }
        // For all fonts subset the Global Subroutines
        // Scan the Global Subr Hashmap recursively on the Gsubrs
        BuildGSubrsUsed(Font);
        if (fonts[Font].privateSubrs>=0)
            // Builds the New Local Subrs index
            NewSubrsIndexNonCID = BuildNewIndex(fonts[Font].SubrsOffsets,hSubrsUsedNonCID,RETURN_OP);
        //Builds the New Global Subrs index
        NewGSubrsIndex = BuildNewIndex(gsubrOffsets,hGSubrsUsed,RETURN_OP);
    }

    /**
     * The function finds for the FD array processed the local subr offset and its 
     * offset array.  
     * @param Font the font
     * @param FD The FDARRAY processed
     */
    protected void BuildFDSubrsOffsets(int Font,int FD)
    {
        // Initiate to -1 to indicate lsubr operator present
        fonts[Font].PrivateSubrsOffset[FD] = -1;
        // Goto beginning of objects
        seek(fonts[Font].fdprivateOffsets[FD]);
        // While in the same object:
        while (getPosition() < fonts[Font].fdprivateOffsets[FD]+fonts[Font].fdprivateLengths[FD])
        {
            getDictItem();
            // If the dictItem is the "Subrs" then find and store offset, 
            if (Objects.equals(key, "Subrs"))
                fonts[Font].PrivateSubrsOffset[FD] = (Integer) args[0] +fonts[Font].fdprivateOffsets[FD];
        }
        //Read the lsubr index if the lsubr was found
        if (fonts[Font].PrivateSubrsOffset[FD] >= 0)
            fonts[Font].PrivateSubrsOffsetsArray[FD] = getIndex(fonts[Font].PrivateSubrsOffset[FD]); 
    }

    /**
     * Function uses ReadAsubr on the glyph used to build the LSubr and Gsubr HashMap.
     * The HashMap (of the lsubr only) is then scanned recursively for Lsubr and Gsubrs
     * calls.  
     * @param Font the font
     * @param FD FD array processed. 0 indicates function was called by non CID font
     * @param SubrOffset the offset to the subr index to calc the bias
     * @param SubrsOffsets the offset array of the subr index
     * @param hSubr HashMap of the subrs used
     * @param lSubr ArrayList of the subrs used
     */
    protected void BuildSubrUsed(int Font,int FD,int SubrOffset,int[] SubrsOffsets, Map hSubr, List lSubr)
    {

        // Calc the Bias for the subr index
        int LBias = CalcBias(SubrOffset,Font);
        
        // For each glyph used find its GID, start & end pos
        for (Object o : glyphsInList) {
            int glyph = (Integer) o;
            int Start = fonts[Font].charstringsOffsets[glyph];
            int End = fonts[Font].charstringsOffsets[glyph + 1];

            // IF CID:
            if (FD >= 0) {
                EmptyStack();
                NumOfHints = 0;
                // Using FDSELECT find the FD Array the glyph belongs to.
                int GlyphFD = fonts[Font].FDSelect[glyph];
                // If the Glyph is part of the FD being processed 
                if (GlyphFD == FD)
                    // Find the Subrs called by the glyph and insert to hash:
                    ReadASubr(Start, End, GBias, LBias, hSubr, lSubr, SubrsOffsets);
            } else
                // If the font is not CID 
                //Find the Subrs called by the glyph and insert to hash:
                ReadASubr(Start, End, GBias, LBias, hSubr, lSubr, SubrsOffsets);
        }
        // For all Lsubrs used, check recursively for Lsubr & Gsubr used
        for (int i=0;i=0)
            {
                // Read and process the subr
                int Start = SubrsOffsets[Subr];
                int End = SubrsOffsets[Subr+1];
                ReadASubr(Start,End,GBias,LBias,hSubr,lSubr,SubrsOffsets);
            }
        }
    }
    
    /**
     * Function scans the Glsubr used ArrayList to find recursive calls 
     * to Gsubrs and adds to Hashmap and ArrayList
     * @param Font the font
     */
    protected void BuildGSubrsUsed(int Font)
    {
        int LBias = 0;
        int SizeOfNonCIDSubrsUsed = 0;
        if (fonts[Font].privateSubrs>=0)
        {
            LBias = CalcBias(fonts[Font].privateSubrs,Font);
            SizeOfNonCIDSubrsUsed = lSubrsUsedNonCID.size();
        }
        
        // For each global subr used 
        for (int i=0;i=0)
            {
                // Read the subr and process
                int Start = gsubrOffsets[Subr];
                int End = gsubrOffsets[Subr+1];
                
                if (fonts[Font].isCID)
                    ReadASubr(Start,End,GBias,0,hGSubrsUsed,lGSubrsUsed,null);
                else
                {
                    ReadASubr(Start,End,GBias,LBias,hSubrsUsedNonCID,lSubrsUsedNonCID,fonts[Font].SubrsOffsets);
                    if (SizeOfNonCIDSubrsUsed < lSubrsUsedNonCID.size())
                    {
                        for (int j=SizeOfNonCIDSubrsUsed;j=0)
                            {
                                // Read the subr and process
                                int LStart = fonts[Font].SubrsOffsets[LSubr];
                                int LEnd = fonts[Font].SubrsOffsets[LSubr+1];
                                ReadASubr(LStart,LEnd,GBias,LBias,hSubrsUsedNonCID,lSubrsUsedNonCID,fonts[Font].SubrsOffsets);
                            }
                        }
                        SizeOfNonCIDSubrsUsed = lSubrsUsedNonCID.size();
                    }
                }
            }
        }
    }

    /**
     * The function reads a subrs (glyph info) between begin and end.
     * Adds calls to a Lsubr to the hSubr and lSubrs.
     * Adds calls to a Gsubr to the hGSubr and lGSubrs.
     * @param begin the start point of the subr
     * @param end the end point of the subr
     * @param GBias the bias of the Global Subrs
     * @param LBias the bias of the Local Subrs
     * @param hSubr the HashMap for the lSubrs
     * @param lSubr the ArrayList for the lSubrs
     * @param LSubrsOffsets The Offsets array of the subroutines
     */
    protected void ReadASubr(int begin,int end,int GBias,int LBias, Map hSubr, List lSubr, int[] LSubrsOffsets)
    {
        // Clear the stack for the subrs
        EmptyStack();
        NumOfHints = 0;
        // Goto beginning of the subr
        seek(begin);
        while (getPosition() < end) {
            // Read the next command
            ReadCommand();
            int pos = getPosition();
            Object TopElement = null;
            if (arg_count > 0) {
                TopElement = args[arg_count - 1];
            }
            if (TopElement == null) {
                TopElement = 0;
            }
            int NumOfArgs = arg_count;
            // Check the modification needed on the Argument Stack according to key;
            HandelStack();
            // a call to a Lsubr
            if (Objects.equals(key, "callsubr"))
            {
                // Verify that arguments are passed 
                if (NumOfArgs > 0)
                {
                    // Calc the index of the Subrs
                    int Subr = (Integer) TopElement + LBias;
                    // If the subr isn't in the HashMap -> Put in
                    if (!hSubr.containsKey(Subr))
                    {
                        hSubr.put(Subr,null);
                        lSubr.add(Subr);
                    }
                    if (LSubrsOffsets != null) {
                        CalcHints(LSubrsOffsets[Subr], LSubrsOffsets[Subr + 1], LBias, GBias, LSubrsOffsets);
                    }
                    seek(pos);
                }                
            }
            // a call to a Gsubr
            else if (Objects.equals(key, "callgsubr"))
            {
                // Verify that arguments are passed 
                if (NumOfArgs > 0)
                {
                    // Calc the index of the Subrs
                    int Subr = (Integer) TopElement + GBias;
                    // If the subr isn't in the HashMap -> Put in
                    if (!hGSubrsUsed.containsKey(Subr))
                    {
                        hGSubrsUsed.put(Subr,null);
                        lGSubrsUsed.add(Subr);
                    }
                    CalcHints(gsubrOffsets[Subr],gsubrOffsets[Subr+1],LBias,GBias,LSubrsOffsets);
                    seek(pos);
                }
            }
            // A call to "stem"
            else if (Objects.equals(key, "hstem") || Objects.equals(key, "vstem") || Objects.equals(key, "hstemhm") || Objects.equals(key, "vstemhm"))
                // Increment the NumOfHints by the number couples of of arguments
                NumOfHints += NumOfArgs/2;
            // A call to "mask"
            else if (Objects.equals(key, "hintmask") || Objects.equals(key, "cntrmask"))
            {
                // Compute the size of the mask
                int SizeOfMask = NumOfHints/8;
                if (NumOfHints%8 != 0 || SizeOfMask == 0)
                    SizeOfMask++;
                // Continue the pointer in SizeOfMask steps
                for (int i=0;i} flush the stack
     */
    protected int StackOpp()
    {
        if (Objects.equals(key, "ifelse"))
            return -3;
        if (Objects.equals(key, "roll") || Objects.equals(key, "put"))
            return -2;
        if (Objects.equals(key, "callsubr") || Objects.equals(key, "callgsubr") || Objects.equals(key, "add") || Objects.equals(key, "sub") ||
                Objects.equals(key, "div") || Objects.equals(key, "mul") || Objects.equals(key, "drop") || Objects.equals(key, "and") ||
                Objects.equals(key, "or") || Objects.equals(key, "eq"))
            return -1;
        if (Objects.equals(key, "abs") || Objects.equals(key, "neg") || Objects.equals(key, "sqrt") || Objects.equals(key, "exch") ||
                Objects.equals(key, "index") || Objects.equals(key, "get") || Objects.equals(key, "not") || Objects.equals(key, "return"))
            return 0;
        if (Objects.equals(key, "random") || Objects.equals(key, "dup"))
            return 1;
        return 2;
    }
    
    /**
     * Empty the Type2 Stack
     *
     */
    protected void EmptyStack()
    {
        // Null the arguments
        for (int i=0; i0)
        {
            args[arg_count-1]=null;
            arg_count--;
        }
    }
    
    /**
     * Add an item to the stack
     *
     */
    protected void PushStack()
    {
        arg_count++;
    }
    
    /**
     * The function reads the next command after the file pointer is set
     */
    protected void ReadCommand()
    {
        key = null;
        boolean gotKey = false;
        // Until a key is found
        while (!gotKey) {
            // Read the first Char
            char b0 = getCard8();
            // decode according to the type1/type2 format
            if (b0 == 28) // the two next bytes represent a short int;
            {
                int first = getCard8();
                int second = getCard8();
                args[arg_count] = first << 8 | second;
                arg_count++;
                continue;
            }
            if (b0 >= 32 && b0 <= 246) // The byte read is the byte;
            {
                args[arg_count] = b0 - 139;
                arg_count++;
                continue;
            }
            if (b0 >= 247 && b0 <= 250) // The byte read and the next byte constitute a short int
            {
                int w = getCard8();
                args[arg_count] = (b0 - 247) * 256 + w + 108;
                arg_count++;
                continue;
            }
            if (b0 >= 251 && b0 <= 254)// Same as above except negative
            {
                int w = getCard8();
                args[arg_count] = -(b0 - 251) * 256 - w - 108;
                arg_count++;
                continue;
            }
            if (b0 == 255)// The next for bytes represent a double.
            {
                int first = getCard8();
                int second = getCard8();
                int third = getCard8();
                int fourth = getCard8();
                args[arg_count] = first << 24 | second << 16 | third << 8 | fourth;
                arg_count++;
                continue;
            }
            if (b0 <= 31) // An operator was found.. Set Key.
            {
                gotKey=true;
                // 12 is an escape command therefore the next byte is a part
                // of this command
                if (b0 == 12)
                {
                    int b1 = getCard8();
                    if (b1>SubrsEscapeFuncs.length-1)
                        b1 = SubrsEscapeFuncs.length-1;
                    key = SubrsEscapeFuncs[b1];
                }
                else
                    key = SubrsFunctions[b0];
            }
        }        
    }
    
    /**
     * The function reads the subroutine and returns the number of the hint in it.
     * If a call to another subroutine is found the function calls recursively.
     * @param begin the start point of the subr
     * @param end the end point of the subr
     * @param LBias the bias of the Local Subrs
     * @param GBias the bias of the Global Subrs
     * @param LSubrsOffsets The Offsets array of the subroutines
     * @return The number of hints in the subroutine read.
     */
    protected int CalcHints(int begin, int end, int LBias, int GBias, int[] LSubrsOffsets)
    {
        // Goto beginning of the subr
        seek(begin);
        while (getPosition() < end)
        {
            // Read the next command
            ReadCommand();
            int pos = getPosition();
            Object TopElement = null;
            if (arg_count>0)
                TopElement = args[arg_count-1];
            int NumOfArgs = arg_count;
            //Check the modification needed on the Argument Stack according to key;
            HandelStack();
            // a call to a Lsubr
            if (Objects.equals(key, "callsubr"))
            {
                if (NumOfArgs>0)
                {
                    int Subr = (Integer) TopElement + LBias;
                    CalcHints(LSubrsOffsets[Subr],LSubrsOffsets[Subr+1],LBias,GBias,LSubrsOffsets);
                    seek(pos);                    
                }
            }
            // a call to a Gsubr
            else if (Objects.equals(key, "callgsubr"))
            {
                if (NumOfArgs>0)
                {
                    int Subr = (Integer) TopElement + GBias;
                    CalcHints(gsubrOffsets[Subr],gsubrOffsets[Subr+1],LBias,GBias,LSubrsOffsets);
                    seek(pos);                    
                }
            }
            // A call to "stem"
            else if (Objects.equals(key, "hstem") || Objects.equals(key, "vstem") || Objects.equals(key, "hstemhm") || Objects.equals(key, "vstemhm"))
                // Increment the NumOfHints by the number couples of of arguments
                NumOfHints += NumOfArgs/2;
            // A call to "mask"
            else if (Objects.equals(key, "hintmask") || Objects.equals(key, "cntrmask"))
            {
                // Compute the size of the mask
                int SizeOfMask = NumOfHints/8;
                if (NumOfHints%8 != 0 || SizeOfMask == 0)
                    SizeOfMask++;
                // Continue the pointer in SizeOfMask steps
                for (int i=0;i Used,byte OperatorForUnusedEntries) throws IOException
    {
        int unusedCount = 0;
        int Offset=0;
        int[] NewOffsets = new int[Offsets.length];
        // Build the Offsets Array for the Subset
        for (int i=0;i i + 1) {
                    Offset += Offsets[i + 1] - Offsets[i];
                }
            } else {
                // Else the same offset is kept in i+1.
                unusedCount++;
            }
        }
        // Offset var determines the size of the object array
        byte[] NewObjects = new byte[Offset+unusedCount];
        // Build the new Object array
        int unusedOffset = 0;
        for (int i=0;i>> 8) & 0xff);
        NewIndex[Place++] = (byte) ((Count >>> 0) & 0xff);
        // Write the offsize field
        NewIndex[Place++] = Offsize;
        // Write the offset array according to the offsize
        for (int newOffset : NewOffsets) {
            // The value to be written
            int Num = newOffset - NewOffsets[0] + 1;
            // Write in bytes according to the offsize
            switch (Offsize) {
                case 4:
                    NewIndex[Place++] = (byte) ((Num >>> 24) & 0xff); // fallthrough
                case 3:
                    NewIndex[Place++] = (byte) ((Num >>> 16) & 0xff); // fallthrough
                case 2:
                    NewIndex[Place++] = (byte) ((Num >>> 8) & 0xff); // fallthrough
                case 1:
                    NewIndex[Place++] = (byte) ((Num) & 0xff);
            }
        }
        // Write the new object array one by one
        for (byte newObject : NewObjects) {
            NewIndex[Place++] = newObject;
        }
        // Return the new index
        return NewIndex;
    }
    
    /**
     * The function builds the new output stream according to the subset process
     * @param Font the font
     * @return the subsetted font stream
     */
    protected byte[] BuildNewFile(int Font)
    {
        // Prepare linked list for new font components
        OutputList = new LinkedList<>();

        // copy the header of the font
        CopyHeader();
                
        // create a name index
        BuildIndexHeader(1,1,1);
        OutputList.addLast(new UInt8Item((char)( 1+fonts[Font].name.length() )));
        OutputList.addLast(new StringItem(fonts[Font].name));
        
        // create the topdict Index
        BuildIndexHeader(1,2,1);
        OffsetItem topdictIndex1Ref = new IndexOffsetItem(2);
        OutputList.addLast(topdictIndex1Ref);
        IndexBaseItem topdictBase = new IndexBaseItem();
        OutputList.addLast(topdictBase);
                
        // Initialize the Dict Items for later use
        OffsetItem charsetRef     = new DictOffsetItem();
        OffsetItem charstringsRef = new DictOffsetItem();
        OffsetItem fdarrayRef     = new DictOffsetItem();
        OffsetItem fdselectRef    = new DictOffsetItem();
        OffsetItem privateRef     = new DictOffsetItem();
        
        // If the font is not CID create the following keys
        if ( !fonts[Font].isCID ) {
            // create a ROS key
            OutputList.addLast(new DictNumberItem(fonts[Font].nstrings));
            OutputList.addLast(new DictNumberItem(fonts[Font].nstrings+1));
            OutputList.addLast(new DictNumberItem(0));
            OutputList.addLast(new UInt8Item((char)12));
            OutputList.addLast(new UInt8Item((char)30));
            // create a CIDCount key
            OutputList.addLast(new DictNumberItem(fonts[Font].nglyphs));
            OutputList.addLast(new UInt8Item((char)12));
            OutputList.addLast(new UInt8Item((char)34));
            // Sivan's comments
            // What about UIDBase (12,35)? Don't know what is it.
            // I don't think we need FontName; the font I looked at didn't have it.
        }
        // Go to the TopDict of the font being processed
        seek(topdictOffsets[Font]);
        // Run until the end of the TopDict
        while (getPosition() < topdictOffsets[Font+1]) {
            int p1 = getPosition();
            getDictItem();
            int p2 = getPosition();
            // The encoding key is disregarded since CID has no encoding
            final boolean isEncodingKey = Objects.equals(key, "Encoding")
                    // These keys will be added manually by the process.
                    || Objects.equals(key, "Private")
                    || Objects.equals(key, "FDSelect")
                    || Objects.equals(key, "FDArray")
                    || Objects.equals(key, "charset")
                    || Objects.equals(key, "CharStrings");
            if (!isEncodingKey) {
                // copy key "as is" to the output list
                OutputList.add(new RangeItem(buf, p1, p2 - p1));
            }
        }
        // Create the FDArray, FDSelect, Charset and CharStrings Keys
        CreateKeys(fdarrayRef,fdselectRef,charsetRef,charstringsRef);
        
        // Mark the end of the top dict area
        OutputList.addLast(new IndexMarkerItem(topdictIndex1Ref,topdictBase));
        
        // Copy the string index

        if (fonts[Font].isCID) 
            OutputList.addLast(getEntireIndexRange(stringIndexOffset));
        // If the font is not CID we need to append new strings.
        // We need 3 more strings: Registry, Ordering, and a FontName for one FD.
        // The total length is at most "Adobe"+"Identity"+63 = 76
        else
            CreateNewStringIndex(Font);
        
        // copy the new subsetted global subroutine index       
        OutputList.addLast(new RangeItem(new RandomAccessFileOrArray(NewGSubrsIndex),0,NewGSubrsIndex.length));
        
        // deal with fdarray, fdselect, and the font descriptors
        // If the font is CID:
        if (fonts[Font].isCID) {
            // copy the FDArray, FDSelect, charset
       
            // Copy FDSelect
            // Mark the beginning
            OutputList.addLast(new MarkerItem(fdselectRef));
            // If an FDSelect exists copy it
            if (fonts[Font].fdselectOffset>=0)
                OutputList.addLast(new RangeItem(buf,fonts[Font].fdselectOffset,fonts[Font].FDSelectLength));
            // Else create a new one
            else
                CreateFDSelect(fdselectRef,fonts[Font].nglyphs);
                           
              // Copy the Charset
            // Mark the beginning and copy entirely 
            OutputList.addLast(new MarkerItem(charsetRef));
            OutputList.addLast(new RangeItem(buf,fonts[Font].charsetOffset,fonts[Font].CharsetLength));
            
            // Copy the FDArray
            // If an FDArray exists
            if (fonts[Font].fdarrayOffset>=0)
            {
                // Mark the beginning
                OutputList.addLast(new MarkerItem(fdarrayRef));
                // Build a new FDArray with its private dicts and their LSubrs
                Reconstruct(Font);
            }
            else
                // Else create a new one
                CreateFDArray(fdarrayRef,privateRef,Font);
           
        }
        // If the font is not CID
        else 
        {
            // create FDSelect
            CreateFDSelect(fdselectRef,fonts[Font].nglyphs);            
            // recreate a new charset
            CreateCharset(charsetRef,fonts[Font].nglyphs);            
            // create a font dict index (fdarray)
            CreateFDArray(fdarrayRef,privateRef,Font);            
        }
        
        // if a private dict exists insert its subsetted version
        if (fonts[Font].privateOffset>=0)
        {
            // Mark the beginning of the private dict
            IndexBaseItem PrivateBase = new IndexBaseItem();
            OutputList.addLast(PrivateBase);
            OutputList.addLast(new MarkerItem(privateRef));

            OffsetItem Subr = new DictOffsetItem();
            // Build and copy the new private dict
            CreateNonCIDPrivate(Font,Subr);
            // Copy the new LSubrs index
            CreateNonCIDSubrs(Font,PrivateBase,Subr);
        }
        
        // copy the charstring index
        OutputList.addLast(new MarkerItem(charstringsRef));

        // Add the subsetted charstring
        OutputList.addLast(new RangeItem(new RandomAccessFileOrArray(NewCharStringsIndex),0,NewCharStringsIndex.length));
        
        // now create the new CFF font        
        int[] currentOffset = new int[1];
        currentOffset[0] = 0;
        // Count and save the offset for each item
        Iterator listIter = OutputList.iterator();
        while ( listIter.hasNext() ) {
            Item item = (Item) listIter.next();
            item.increment(currentOffset);
        }
        // Compute the Xref for each of the offset items
        listIter = OutputList.iterator();
        while ( listIter.hasNext() ) {
            Item item = (Item) listIter.next();
            item.xref();
        }
        
        int size = currentOffset[0];
        byte[] b = new byte[size];
        
        // Emit all the items into the new byte array
        listIter = OutputList.iterator();
        while ( listIter.hasNext() ) {
            Item item = (Item) listIter.next();
            item.emit(b);
        }
        // Return the new stream
        return b;
    }

    /**
     * Function Copies the header from the original fileto the output list
     */
    protected void CopyHeader()
    {
        seek(0);
        int major = getCard8();
        int minor = getCard8();
        int hdrSize = getCard8();
        int offSize = getCard8();
        nextIndexOffset = hdrSize;
        OutputList.addLast(new RangeItem(buf,0,hdrSize));
    }

    /**
     * Function Build the header of an index
     * @param Count the count field of the index
     * @param Offsize the offsize field of the index
     * @param First the first offset of the index
     */
    protected void BuildIndexHeader(int Count,int Offsize,int First)
    {
        // Add the count field
        OutputList.addLast(new UInt16Item((char)Count)); // count
        // Add the offsize field
        OutputList.addLast(new UInt8Item((char)Offsize)); // offSize
        // Add the first offset according to the offsize
        switch(Offsize){
            case 1:
                OutputList.addLast(new UInt8Item((char)First)); // first offset
                break;
            case 2:
                OutputList.addLast(new UInt16Item((char)First)); // first offset
                break;
            case 3:
                OutputList.addLast(new UInt24Item((char)First)); // first offset
                break;
            case 4:
                OutputList.addLast(new UInt32Item((char)First)); // first offset
                break;
            default:
                break;    
        }
    }
    
    /**
     * Function adds the keys into the TopDict
     * @param fdarrayRef OffsetItem for the FDArray
     * @param fdselectRef OffsetItem for the FDSelect
     * @param charsetRef OffsetItem for the CharSet
     * @param charstringsRef OffsetItem for the CharString
     */
    protected void CreateKeys(OffsetItem fdarrayRef,OffsetItem fdselectRef,OffsetItem charsetRef,OffsetItem charstringsRef)
    {
        // create an FDArray key
        OutputList.addLast(fdarrayRef);
        OutputList.addLast(new UInt8Item((char)12));
        OutputList.addLast(new UInt8Item((char)36));
        // create an FDSelect key
        OutputList.addLast(fdselectRef);
        OutputList.addLast(new UInt8Item((char)12));
        OutputList.addLast(new UInt8Item((char)37));
        // create an charset key
        OutputList.addLast(charsetRef);
        OutputList.addLast(new UInt8Item((char)15));
        // create a CharStrings key
        OutputList.addLast(charstringsRef);
        OutputList.addLast(new UInt8Item((char)17));
    }
    
    /**
     * Function takes the original string item and adds the new strings
     * to accommodate the CID rules
     * @param Font the font
     */
    protected void CreateNewStringIndex(int Font)
    {
        String fdFontName = fonts[Font].name+"-OneRange";
        if (fdFontName.length() > 127)
            fdFontName = fdFontName.substring(0,127);
        String extraStrings = "Adobe"+"Identity"+fdFontName;
        
        int origStringsLen = stringOffsets[stringOffsets.length-1]
        - stringOffsets[0];
        int stringsBaseOffset = stringOffsets[0]-1;
        
        byte stringsIndexOffSize;
        if (origStringsLen+extraStrings.length() <= 0xff) stringsIndexOffSize = 1;
        else if (origStringsLen+extraStrings.length() <= 0xffff) stringsIndexOffSize = 2;
        else if (origStringsLen+extraStrings.length() <= 0xffffff) stringsIndexOffSize = 3;
        else stringsIndexOffSize = 4;
        
        OutputList.addLast(new UInt16Item((char)((stringOffsets.length-1)+3))); // count
        OutputList.addLast(new UInt8Item((char)stringsIndexOffSize)); // offSize
        for (int stringOffset : stringOffsets)
            OutputList.addLast(new IndexOffsetItem(stringsIndexOffSize,
                    stringOffset - stringsBaseOffset));
        int currentStringsOffset = stringOffsets[stringOffsets.length-1]
        - stringsBaseOffset;
        //l.addLast(new IndexOffsetItem(stringsIndexOffSize,currentStringsOffset));
        currentStringsOffset += "Adobe".length();
        OutputList.addLast(new IndexOffsetItem(stringsIndexOffSize,currentStringsOffset));
        currentStringsOffset += "Identity".length();
        OutputList.addLast(new IndexOffsetItem(stringsIndexOffSize,currentStringsOffset));
        currentStringsOffset += fdFontName.length();
        OutputList.addLast(new IndexOffsetItem(stringsIndexOffSize,currentStringsOffset));
        
        OutputList.addLast(new RangeItem(buf,stringOffsets[0],origStringsLen));
        OutputList.addLast(new StringItem(extraStrings));
    }
     
    /**
     * Function creates new FDSelect for non-CID fonts.
     * The FDSelect built uses a single range for all glyphs
     * @param fdselectRef OffsetItem for the FDSelect
     * @param nglyphs the number of glyphs in the font
     */
    protected void CreateFDSelect(OffsetItem fdselectRef,int nglyphs)
    {
        OutputList.addLast(new MarkerItem(fdselectRef));
        OutputList.addLast(new UInt8Item((char)3)); // format identifier
        OutputList.addLast(new UInt16Item((char)1)); // nRanges
        
        OutputList.addLast(new UInt16Item((char)0)); // Range[0].firstGlyph
        OutputList.addLast(new UInt8Item((char)0)); // Range[0].fd
        
        OutputList.addLast(new UInt16Item((char)nglyphs)); // sentinel
    }

    /**
     * Function creates new CharSet for non-CID fonts.
     * The CharSet built uses a single range for all glyphs
     * @param charsetRef OffsetItem for the CharSet
     * @param nglyphs the number of glyphs in the font
     */
    protected void CreateCharset(OffsetItem charsetRef,int nglyphs)
    {
        OutputList.addLast(new MarkerItem(charsetRef));
        OutputList.addLast(new UInt8Item((char)2)); // format identifier
        OutputList.addLast(new UInt16Item((char)1)); // first glyph in range (ignore .notdef)
        OutputList.addLast(new UInt16Item((char)(nglyphs-1))); // nLeft
    }
    
    /**
     * Function creates new FDArray for non-CID fonts.
     * The FDArray built has only the "Private" operator that points to the font's
     * original private dict 
     * @param fdarrayRef OffsetItem for the FDArray
     * @param privateRef OffsetItem for the Private Dict
     * @param Font the font
     */
    protected void CreateFDArray(OffsetItem fdarrayRef,OffsetItem privateRef,int Font)
    {
        OutputList.addLast(new MarkerItem(fdarrayRef));
        // Build the header (count=offsize=first=1)
        BuildIndexHeader(1,1,1);
        
        // Mark
        OffsetItem privateIndex1Ref = new IndexOffsetItem(1);
        OutputList.addLast(privateIndex1Ref);
        IndexBaseItem privateBase = new IndexBaseItem();
        // Insert the private operands and operator
        OutputList.addLast(privateBase);
        // Calc the new size of the private after subsetting
        // Origianl size
        int NewSize = fonts[Font].privateLength;
        // Calc the original size of the Subr offset in the private
        int OrgSubrsOffsetSize = CalcSubrOffsetSize(fonts[Font].privateOffset,fonts[Font].privateLength);
        // Increase the ptivate's size
        if (OrgSubrsOffsetSize != 0)
            NewSize += 5-OrgSubrsOffsetSize;
        OutputList.addLast(new DictNumberItem(NewSize));
        OutputList.addLast(privateRef);
        OutputList.addLast(new UInt8Item((char)18)); // Private
        
        OutputList.addLast(new IndexMarkerItem(privateIndex1Ref,privateBase));
    }
    
    /**
     * Function reconstructs the FDArray, PrivateDict and LSubr for CID fonts
     * @param Font the font
     */
    void Reconstruct(int Font)
    {
        // Init for later use
        OffsetItem[] fdPrivate = new DictOffsetItem[fonts[Font].FDArrayOffsets.length-1];
        IndexBaseItem[] fdPrivateBase = new IndexBaseItem[fonts[Font].fdprivateOffsets.length]; 
        OffsetItem[] fdSubrs = new DictOffsetItem[fonts[Font].fdprivateOffsets.length];
        // Reconstruct each type
        ReconstructFDArray(Font,fdPrivate);
        ReconstructPrivateDict(Font,fdPrivate,fdPrivateBase,fdSubrs);
        ReconstructPrivateSubrs(Font,fdPrivateBase,fdSubrs);
    }

    /**
     * Function subsets the FDArray and builds the new one with new offsets
     * @param Font The font
     * @param fdPrivate OffsetItem Array (one for each FDArray)
     */
    void ReconstructFDArray(int Font,OffsetItem[] fdPrivate)
    {
        // Build the header of the index
        BuildIndexHeader(fonts[Font].FDArrayCount,fonts[Font].FDArrayOffsize,1);

        // For each offset create an Offset Item
        OffsetItem[] fdOffsets = new IndexOffsetItem[fonts[Font].FDArrayOffsets.length-1];
        for (int i=0;i= 0)
            {                
                OutputList.addLast(new SubrMarkerItem(fdSubrs[i],fdPrivateBase[i]));
                OutputList.addLast(new RangeItem(new RandomAccessFileOrArray(NewLSubrsIndex[i]),0,NewLSubrsIndex[i].length));
            }
        }
    }

    /**
     * Calculates how many byte it took to write the offset for the subrs in a specific
     * private dict.
     * @param Offset The Offset for the private dict
     * @param Size The size of the private dict
     * @return The size of the offset of the subrs in the private dict
     */
    int CalcSubrOffsetSize(int Offset,int Size)
    {
        // Set the size to 0
        int OffsetSize = 0;
        // Go to the beginning of the private dict
        seek(Offset);
        // Go until the end of the private dict 
        while (getPosition() < Offset+Size)
        {
            int p1 = getPosition();
            getDictItem();
            int p2 = getPosition();
            // When reached to the subrs offset
            if (Objects.equals(key, "Subrs")) {
                // The Offsize (minus the subrs key)
                OffsetSize = p2-p1-1;
            }
            // All other keys are ignored
        }
        // return the size
        return OffsetSize;
    }
    
    /**
     * Function computes the size of an index
     * @param indexOffset The offset for the computed index
     * @return The size of the index
     */
    protected int countEntireIndexRange(int indexOffset) 
    {
        // Go to the beginning of the index 
        seek(indexOffset);
        // Read the count field
        int count = getCard16();
        // If count==0 -> size=2
        if (count==0) 
            return 2;
        else 
        {
            // Read the offsize field
            int indexOffSize = getCard8();
            // Go to the last element of the offset array
            seek(indexOffset+2+1+count*indexOffSize);
            // The size of the object array is the value of the last element-1
            int size = getOffset(indexOffSize)-1;
            // Return the size of the entire index
            return 2+1+(count+1)*indexOffSize+size;
        }
    }
    
    /**
     * The function creates a private dict for a font that was not CID
     * All the keys are copied as is except for the subrs key 
     * @param Font the font
     * @param Subr The OffsetItem for the subrs of the private 
     */
    void CreateNonCIDPrivate(int Font,OffsetItem Subr)
    {
        // Go to the beginning of the private dict and read until the end
        seek(fonts[Font].privateOffset);
        while (getPosition() < fonts[Font].privateOffset+fonts[Font].privateLength)
        {
            int p1 = getPosition();
            getDictItem();
            int p2 = getPosition();
            // If the dictItem is the "Subrs" then, 
            // use marker for offset and write operator number
            if (Objects.equals(key, "Subrs")) {
                OutputList.addLast(Subr);
                OutputList.addLast(new UInt8Item((char)19)); // Subrs
            }
            // Else copy the entire range
            else
                OutputList.addLast(new RangeItem(buf,p1,p2-p1));
        }
    }
    
    /**
     * the function marks the beginning of the subrs index and adds the subsetted subrs
     * index to the output list. 
     * @param Font the font
     * @param PrivateBase IndexBaseItem for the private that's referencing to the subrs
     * @param Subrs OffsetItem for the subrs
     */
    void CreateNonCIDSubrs(int Font,IndexBaseItem PrivateBase,OffsetItem Subrs)
    {
        // Mark the beginning of the Subrs index
        OutputList.addLast(new SubrMarkerItem(Subrs,PrivateBase));
        // Put the subsetted new subrs index
        if (NewSubrsIndexNonCID != null) {
            OutputList.addLast(new RangeItem(new RandomAccessFileOrArray(NewSubrsIndexNonCID), 0, NewSubrsIndexNonCID.length));
        }
    }    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy