# PDCIDFontType0.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;

    import java.awt.geom.AffineTransform;
    import java.awt.geom.GeneralPath;
    import java.awt.geom.Point2D;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.fontbox.FontBoxFont;
    import org.apache.fontbox.cff.CFFCIDFont;
    import org.apache.fontbox.cff.CFFFont;
    import org.apache.fontbox.cff.CFFParser;
    import org.apache.fontbox.cff.CFFType1Font;
    import org.apache.fontbox.cff.Type2CharString;
    import org.apache.fontbox.util.BoundingBox;
    import org.apache.pdfbox.cos.COSDictionary;
    import org.apache.pdfbox.io.RandomAccessRead;
    import org.apache.pdfbox.pdmodel.common.PDRectangle;
    import org.apache.pdfbox.pdmodel.common.PDStream;
    import org.apache.pdfbox.util.Matrix;

    import static org.apache.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint;

    /**
     * Type 0 CIDFont (CFF).
     *
     * @author Ben Litchfield
     * @author John Hewson
     */
    public class PDCIDFontType0 extends PDCIDFont
    {
        private static final Log LOG = LogFactory.getLog(PDCIDFontType0.class);

        private final CFFCIDFont cidFont;  // Top DICT that uses CIDFont operators
        private final FontBoxFont t1Font; // Top DICT that does not use CIDFont operators

        private final Map<Integer, Float> glyphHeights = new HashMap<>();
        private final boolean isEmbedded;
        private final boolean isDamaged;
        private final AffineTransform fontMatrixTransform;
        private Float avgWidth = null;
        private Matrix fontMatrix;
        private BoundingBox fontBBox;
        private int[] cid2gid = null;

        /**
         * Constructor.
         *
         * @param fontDictionary The font dictionary according to the PDF specification.
         * @param parent The parent font.
         */
        public PDCIDFontType0(COSDictionary fontDictionary, PDType0Font parent) throws IOException
        {
            super(fontDictionary, parent);

            boolean fontIsDamaged = false;
            CFFFont cffFont = null;
            PDFontDescriptor fd = getFontDescriptor();
            if (fd != null)
            {
                PDStream ff3Stream = fd.getFontFile3();
                if (ff3Stream != null)
                {
                    try (RandomAccessRead randomAccessRead = ff3Stream.getCOSObject().createView())
                    {
                        if (randomAccessRead.length() > 0 && randomAccessRead.peek() == '%')
                        {
                            // PDFBOX-2642 contains a corrupt PFB font instead of a CFF
                            LOG.warn("Found PFB but expected embedded CFF font " + fd.getFontName());
                            fontIsDamaged = true;
                        }
                        else
                        {
                            CFFParser cffParser = new CFFParser();
                            cffFont = cffParser.parse(randomAccessRead).get(0);
                        }
                    }
                    catch (IOException e)
                    {
                        LOG.error("Can't read the embedded CFF font " + fd.getFontName(), e);
                        fontIsDamaged = true;
                    }
                }
            }

            if (cffFont != null)
            {
                // embedded
                if (cffFont instanceof CFFCIDFont)
                {
                    cidFont = (CFFCIDFont)cffFont;
                    t1Font = null;
                }
                else
                {
                    cidFont = null;
                    t1Font = cffFont;
                }
                cid2gid = readCIDToGIDMap();
                isEmbedded = true;
                isDamaged = false;
            }
            else
            {
                // find font or substitute
                CIDFontMapping mapping = FontMappers.instance()
                                                    .getCIDFont(getBaseFont(), getFontDescriptor(),
                                                                getCIDSystemInfo());
                FontBoxFont font;
                if (mapping.isCIDFont())
                {
                    cffFont = mapping.getFont().getCFF().getFont();
                    if (cffFont instanceof CFFCIDFont)
                    {
                        cidFont = (CFFCIDFont) cffFont;
                        t1Font = null;
                        font = cidFont;
                    }
                    else
                    {
                        // PDFBOX-3515: OpenType fonts are loaded as CFFType1Font
                        CFFType1Font f = (CFFType1Font) cffFont;
                        cidFont = null;
                        t1Font = f;
                        font = f;
                    }
                }
                else
                {
                    cidFont = null;
                    t1Font = mapping.getTrueTypeFont();
                    font = t1Font;
                }

                if (mapping.isFallback())
                {
                    LOG.warn("Using fallback " + font.getName() + " for CID-keyed font " +
                             getBaseFont());
                }
                isEmbedded = false;
                isDamaged = fontIsDamaged;
            }
            fontMatrixTransform = getFontMatrix().createAffineTransform();
            fontMatrixTransform.scale(1000, 1000);
        }

        @Override
        public final Matrix getFontMatrix()
        {
            if (fontMatrix == null)
            {
                List<Number> numbers;
                if (cidFont != null)
                {
                    numbers = cidFont.getFontMatrix();
                }
                else
                {
                    try
                    {
                        numbers = t1Font.getFontMatrix();
                    }
                    catch (IOException e)
                    {
                        LOG.debug("Couldn't get font matrix - returning default value", e);
                        return new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
                    }
                }

                if (numbers != null && numbers.size() == 6)
                {
                    fontMatrix = new Matrix(numbers.get(0).floatValue(), numbers.get(1).floatValue(),
                                            numbers.get(2).floatValue(), numbers.get(3).floatValue(),
                                            numbers.get(4).floatValue(), numbers.get(5).floatValue());
                }
                else
                {
                    fontMatrix = new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
                }
            }
            return fontMatrix;
        }

        @Override
        public BoundingBox getBoundingBox()
        {
            if (fontBBox == null)
            {
                fontBBox = generateBoundingBox();
            }
            return fontBBox;
        }

        private BoundingBox generateBoundingBox()
        {
            if (getFontDescriptor() != null) {
                PDRectangle bbox = getFontDescriptor().getFontBoundingBox();
                if (bbox != null && (Float.compare(bbox.getLowerLeftX(),0) != 0 ||
                    Float.compare(bbox.getLowerLeftY(),0) != 0 ||
                    Float.compare(bbox.getUpperRightX(),0) != 0 ||
                    Float.compare(bbox.getUpperRightY(),0) != 0))
                {
                    return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(),
                                              bbox.getUpperRightX(), bbox.getUpperRightY());
                }
            }
            try
            {
                return cidFont != null ? cidFont.getFontBBox() : t1Font.getFontBBox();
            }
            catch (IOException e)
            {
                LOG.debug("Couldn't get font bounding box - returning default value", e);
                return new BoundingBox();
            }
        }

        /**
         * Returns the embedded CFF CIDFont, or null if the substitute is not a CFF font.
         */
        public CFFFont getCFFFont()
        {
            if (cidFont != null)
            {
                return cidFont;
            }
            else if (t1Font instanceof CFFType1Font)
            {
                return (CFFType1Font)t1Font;
            }
            else
            {
                return null;
            }
        }

        /**
         * Returns the embedded or substituted font.
         */
        public FontBoxFont getFontBoxFont()
        {
            if (cidFont != null)
            {
                return cidFont;
            }
            else
            {
                return t1Font;
            }
        }

        /**
         * Returns the Type 2 charstring for the given CID, or null if the substituted font does not
         * contain Type 2 charstrings.
         *
         * @param cid CID
         * @throws IOException if the charstring could not be read
         */
        public Type2CharString getType2CharString(int cid) throws IOException
        {
            if (cidFont != null)
            {
                return cidFont.getType2CharString(cid);
            }
            else if (t1Font instanceof CFFType1Font)
            {
                return ((CFFType1Font)t1Font).getType2CharString(cid);
            }
            else
            {
                return null;
            }
        }

        /**
         * Returns the name of the glyph with the given character code. This is done by looking up the
         * code in the parent font's ToUnicode map and generating a glyph name from that.
         */
        private String getGlyphName(int code)
        {
            String unicodes = parent.toUnicode(code);
            if (unicodes == null)
            {
                return ".notdef";
            }
            return getUniNameOfCodePoint(unicodes.codePointAt(0));
        }

        @Override
        public GeneralPath getPath(int code) throws IOException
        {
            int cid = codeToCID(code);
            if (cid2gid != null && isEmbedded)
            {
                // PDFBOX-4093: despite being a type 0 font, there is a CIDToGIDMap
                cid = cid2gid[cid];
            }
            Type2CharString charstring = getType2CharString(cid);
            if (charstring != null)
            {
                return charstring.getPath();
            }
            else if (isEmbedded && t1Font instanceof CFFType1Font)
            {
                return ((CFFType1Font)t1Font).getType2CharString(cid).getPath();
            }
            else
            {
                return t1Font.getPath(getGlyphName(code));
            }
        }

        @Override
        public GeneralPath getNormalizedPath(int code) throws IOException
        {
            return getPath(code);
        }

        @Override
        public boolean hasGlyph(int code) throws IOException
        {
            int cid = codeToCID(code);
            Type2CharString charstring = getType2CharString(cid);
            if (charstring != null)
            {
                return charstring.getGID() != 0;
            }
            else if (isEmbedded && t1Font instanceof CFFType1Font)
            {
                return ((CFFType1Font)t1Font).getType2CharString(cid).getGID() != 0;
            }
            else
            {
                return t1Font.hasGlyph(getGlyphName(code));
            }
        }

        /**
         * Returns the CID for the given character code. If not found then CID 0 is returned.
         *
         * @param code character code
         * @return CID
         */
        @Override
        public int codeToCID(int code)
        {
            return parent.getCMap().toCID(code);
        }

        @Override
        public int codeToGID(int code)
        {
            int cid = codeToCID(code);
            if (cidFont != null)
            {
                // The CIDs shall be used to determine the GID value for the glyph procedure using the
                // charset table in the CFF program
                return cidFont.getCharset().getGIDForCID(cid);
            }
            else
            {
                // The CIDs shall be used directly as GID values
                return cid;
            }
        }

        @Override
        public byte[] encode(int unicode)
        {
            // todo: we can use a known character collection CMap for a CIDFont
            //       and an Encoding for Type 1-equivalent
            throw new UnsupportedOperationException();
        }

        @Override
        public byte[] encodeGlyphId(int glyphId)
        {
            throw new UnsupportedOperationException();
        }

        @Override
        public float getWidthFromFont(int code) throws IOException
        {
            int cid = codeToCID(code);
            float width;
            if (cidFont != null)
            {
                width = getType2CharString(cid).getWidth();
            }
            else if (isEmbedded && t1Font instanceof CFFType1Font)
            {
                width = ((CFFType1Font)t1Font).getType2CharString(cid).getWidth();
            }
            else
            {
                width = t1Font.getWidth(getGlyphName(code));
            }

            Point2D p = new Point2D.Float(width, 0);
            fontMatrixTransform.transform(p, p);
            return (float)p.getX();
        }

        @Override
        public boolean isEmbedded()
        {
            return isEmbedded;
        }

        @Override
        public boolean isDamaged()
        {
            return isDamaged;
        }

        @Override
        public float getHeight(int code) throws IOException
        {
            int cid = codeToCID(code);

            float height;
            if (!glyphHeights.containsKey(cid))
            {
                height = (float) getType2CharString(cid).getBounds().getHeight();
                glyphHeights.put(cid, height);
            }
            else
            {
                height = glyphHeights.get(cid);
            }
            return height;
        }

        @Override
        public float getAverageFontWidth()
        {
            if (avgWidth == null)
            {
                avgWidth = getAverageCharacterWidth();
            }
            return avgWidth;
        }

        // todo: this is a replacement for FontMetrics method
        private float getAverageCharacterWidth()
        {
            // todo: not implemented, highly suspect
            return 500;
        }

    }