# DictionaryEncoding.java

    /*
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.apache.pdfbox.pdmodel.font.encoding;

    import java.util.HashMap;
    import java.util.Map;
    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSBase;
    import org.apache.pdfbox.cos.COSDictionary;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.cos.COSNumber;

    /**
     * This will perform the encoding from a dictionary.
     *
     * @author Ben Litchfield
     */
    public class DictionaryEncoding extends Encoding
    {
        private final COSDictionary encoding;
        private final Encoding baseEncoding;
        private final Map<Integer, String> differences = new HashMap<>();

        /**
         * Creates a new DictionaryEncoding for embedding.
         *
         * @param baseEncoding
         * @param differences
         */
        public DictionaryEncoding(COSName baseEncoding, COSArray differences)
        {
            encoding = new COSDictionary();
            encoding.setItem(COSName.NAME, COSName.ENCODING);
            encoding.setItem(COSName.DIFFERENCES, differences);
            if (baseEncoding != COSName.STANDARD_ENCODING)
            {
                encoding.setItem(COSName.BASE_ENCODING, baseEncoding);
                this.baseEncoding = Encoding.getInstance(baseEncoding);
            }
            else
            {
                this.baseEncoding = Encoding.getInstance(baseEncoding);
            }

            if (this.baseEncoding == null)
            {
                throw new IllegalArgumentException("Invalid encoding: " + baseEncoding);
            }

            codeToName.putAll(this.baseEncoding.codeToName);
            inverted.putAll(this.baseEncoding.inverted);
            applyDifferences();
        }

        /**
         * Creates a new DictionaryEncoding for a Type 3 font from a PDF.
         *
         * @param fontEncoding The Type 3 encoding dictionary.
         */
        public DictionaryEncoding(COSDictionary fontEncoding)
        {
            encoding = fontEncoding;
            baseEncoding = null;
            applyDifferences();
        }

        /**
         * Creates a new DictionaryEncoding from a PDF.
         *
         * @param fontEncoding The encoding dictionary.
         * @param isNonSymbolic True if the font is non-symbolic. False for Type 3 fonts.
         * @param builtIn The font's built-in encoding. Null for Type 3 fonts.
         */
        public DictionaryEncoding(COSDictionary fontEncoding, boolean isNonSymbolic, Encoding builtIn)
        {
            encoding = fontEncoding;

            Encoding base = null;
            boolean hasBaseEncoding = encoding.containsKey(COSName.BASE_ENCODING);
            if (hasBaseEncoding)
            {
                COSName name = encoding.getCOSName(COSName.BASE_ENCODING);
                base = Encoding.getInstance(name); // null when the name is invalid
            }

            if (base == null)
            {
                if (isNonSymbolic)
                {
                    // Otherwise, for a nonsymbolic font, it is StandardEncoding
                    base = StandardEncoding.INSTANCE;
                }
                else
                {
                    // and for a symbolic font, it is the font's built-in encoding.
                    if (builtIn != null)
                    {
                        base = builtIn;
                    }
                    else
                    {
                        // triggering this error indicates a bug in PDFBox. Every font should always have
                        // a built-in encoding, if not, we parsed it incorrectly.
                        throw new IllegalArgumentException("Symbolic fonts must have a built-in " +
                                                           "encoding");
                    }
                }
            }
            baseEncoding = base;

            codeToName.putAll(baseEncoding.codeToName);
            inverted.putAll(baseEncoding.inverted);
            applyDifferences();
        }

        private void applyDifferences()
        {
            // now replace with the differences
            COSArray diffArray = encoding.getCOSArray(COSName.DIFFERENCES);
            if (diffArray == null)
            {
                return;
            }
            int currentIndex = -1;
            for (int i = 0; i < diffArray.size(); i++)
            {
                COSBase next = diffArray.getObject(i);
                if( next instanceof COSNumber)
                {
                    currentIndex = ((COSNumber)next).intValue();
                }
                else if( next instanceof COSName )
                {
                    COSName name = (COSName)next;
                    overwrite(currentIndex, name.getName());
                    this.differences.put(currentIndex, name.getName());
                    currentIndex++;
                }
            }
        }

        /**
         * Returns the base encoding. Will be null for Type 3 fonts.
         */
        public Encoding getBaseEncoding()
        {
            return baseEncoding;
        }

        /**
         * Returns the Differences array.
         */
        public Map<Integer, String> getDifferences()
        {
            return differences;
        }

        @Override
        public COSBase getCOSObject()
        {
            return encoding;
        }

        @Override
        public String getEncodingName()
        {
            if (baseEncoding == null)
            {
                // In type 3 the /Differences array shall specify the complete character encoding
                return "differences";
            }
            return baseEncoding.getEncodingName() + " with differences";
        }
    }