# PDExtendedGraphicsState.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.graphics.state;

    import java.io.IOException;

    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSBase;
    import org.apache.pdfbox.cos.COSDictionary;
    import org.apache.pdfbox.cos.COSFloat;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.cos.COSNumber;
    import org.apache.pdfbox.pdmodel.common.COSObjectable;
    import org.apache.pdfbox.pdmodel.graphics.PDFontSetting;
    import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
    import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode;

    /**
     * An extended graphics state dictionary.
     *
     * @author Ben Litchfield
     */
    public class PDExtendedGraphicsState implements COSObjectable
    {
        private final COSDictionary dict;

        /**
         * Default constructor, creates blank graphics state.
         */
        public PDExtendedGraphicsState()
        {
            dict = new COSDictionary();
            dict.setItem(COSName.TYPE, COSName.EXT_G_STATE);
        }

        /**
         * Create a graphics state from an existing dictionary.
         *
         * @param dictionary The existing graphics state.
         */
        public PDExtendedGraphicsState(COSDictionary dictionary)
        {
            dict = dictionary;
        }

        /**
         * This will implement the gs operator.
         *
         * @param gs The state to copy this dictionaries values into.
         *
         * @throws IOException If there is an error copying font information.
         */
        public void copyIntoGraphicsState( PDGraphicsState gs ) throws IOException
        {
            for( COSName key : dict.keySet() )
            {
                if( key.equals( COSName.LW ) )
                {
                    gs.setLineWidth( defaultIfNull( getLineWidth(), 1 ) );
                }
                else if( key.equals( COSName.LC ) )
                {
                    gs.setLineCap( getLineCapStyle() );
                }
                else if( key.equals( COSName.LJ ) )
                {
                    gs.setLineJoin( getLineJoinStyle() );
                }
                else if( key.equals( COSName.ML ) )
                {
                    gs.setMiterLimit( defaultIfNull( getMiterLimit(), 10 ) );
                }
                else if( key.equals( COSName.D ) )
                {
                    gs.setLineDashPattern( getLineDashPattern() );
                }
                else if( key.equals( COSName.RI ) )
                {
                    gs.setRenderingIntent( getRenderingIntent() );
                }
                else if( key.equals( COSName.OPM ) )
                {
                    Integer overprintMode = getOverprintMode();
                    gs.setOverprintMode(overprintMode != null ? overprintMode : 0);
                }
                else if( key.equals( COSName.OP ) )
                {
                    gs.setOverprint( getStrokingOverprintControl());
                }
                else if( key.equals( COSName.OP_NS ) )
                {
                    gs.setNonStrokingOverprint(getNonStrokingOverprintControl());
                }
                else if( key.equals( COSName.FONT ) )
                {
                    PDFontSetting setting = getFontSetting();
                    if (setting != null)
                    {
                        gs.getTextState().setFont( setting.getFont() );
                        gs.getTextState().setFontSize( setting.getFontSize() );
                    }
                }
                else if( key.equals( COSName.FL ) )
                {
                    gs.setFlatness( defaultIfNull( getFlatnessTolerance(), 1.0f ) );
                }
                else if( key.equals( COSName.SM ) )
                {
                    gs.setSmoothness( defaultIfNull( getSmoothnessTolerance(), 0 ) );
                }
                else if( key.equals( COSName.SA ) )
                {
                    gs.setStrokeAdjustment( getAutomaticStrokeAdjustment() );
                }
                else if( key.equals( COSName.CA ) )
                {
                    gs.setAlphaConstant( defaultIfNull( getStrokingAlphaConstant(), 1.0f ) );
                }
                else if( key.equals( COSName.CA_NS ) )
                {
                    gs.setNonStrokeAlphaConstant( defaultIfNull( getNonStrokingAlphaConstant(), 1.0f ) );
                }
                else if( key.equals( COSName.AIS ) )
                {
                    gs.setAlphaSource( getAlphaSourceFlag() );
                }
                else if( key.equals( COSName.TK ) )
                {
                    gs.getTextState().setKnockoutFlag( getTextKnockoutFlag() );
                }
                else if( key.equals( COSName.SMASK ) )
                {
                    PDSoftMask softmask = getSoftMask();
                    if (softmask != null)
                    {
                        // Softmask must know the CTM at the time the ExtGState is activated. Read
                        // https://bugs.ghostscript.com/show_bug.cgi?id=691157#c7 for a good explanation.
                        softmask.setInitialTransformationMatrix(gs.getCurrentTransformationMatrix().clone());
                    }
                    gs.setSoftMask(softmask);
                }
                else if( key.equals( COSName.BM ) )
                {
                    gs.setBlendMode( getBlendMode() );
                }
                else if (key.equals(COSName.TR))
                {
                    if (dict.containsKey(COSName.TR2))
                    {
                        // "If both TR and TR2 are present in the same graphics state parameter dictionary,
                        // TR2 shall take precedence."
                        continue;
                    }
                    gs.setTransfer(getTransfer());
                }
                else if (key.equals(COSName.TR2))
                {
                    gs.setTransfer(getTransfer2());
                }
            }
        }

        /**
         * Returns the provided default value in case 'standard' value is <code>null</code>. To be used
         * in cases unboxing may lead to a NPE.
         *
         * @param standardValue 'standard' value
         * @param defaultValue default value
         *
         * @return 'standard' value if not <code>null</code> otherwise default value
         */
        private float defaultIfNull(Float standardValue, float defaultValue)
        {
            return standardValue != null ? standardValue : defaultValue;
        }

        /**
         * This will get the underlying dictionary that this class acts on.
         *
         * @return The underlying dictionary for this class.
         */
        @Override
        public COSDictionary getCOSObject()
        {
            return dict;
        }

        /**
         * This will get the line width.  This will return null if there is no line width
         *
         * @return null or the LW value of the dictionary.
         */
        public Float getLineWidth()
        {
            return getFloatItem( COSName.LW );
        }

        /**
         * This will set the line width.
         *
         * @param width The line width for the object.
         */
        public void setLineWidth( Float width )
        {
            setFloatItem( COSName.LW, width );
        }

        /**
         * This will get the line cap style.
         *
         * @return null or the LC value of the dictionary.
         */
        public int getLineCapStyle()
        {
            return dict.getInt( COSName.LC );
        }

        /**
         * This will set the line cap style for the graphics state.
         *
         * @param style The new line cap style to set.
         */
        public void setLineCapStyle( int style )
        {
            dict.setInt(COSName.LC, style);
        }

        /**
         * This will get the line join style.
         *
         * @return null or the LJ value in the dictionary.
         */
        public int getLineJoinStyle()
        {
            return dict.getInt( COSName.LJ );
        }

        /**
         * This will set the line join style.
         *
         * @param style The new line join style.
         */
        public void setLineJoinStyle( int style )
        {
            dict.setInt(COSName.LJ, style);
        }

        /**
         * This will get the miter limit.
         *
         * @return null or the ML value in the dictionary.
         */
        public Float getMiterLimit()
        {
            return getFloatItem( COSName.ML );
        }

        /**
         * This will set the miter limit for the graphics state.
         *
         * @param miterLimit The new miter limit value
         */
        public void setMiterLimit( Float miterLimit )
        {
            setFloatItem( COSName.ML, miterLimit );
        }

        /**
         * This will get the dash pattern.
         *
         * @return null or the D value in the dictionary.
         */
        public PDLineDashPattern getLineDashPattern()
        {
            PDLineDashPattern retval = null;
            COSArray dp = dict.getCOSArray(COSName.D);
            if (dp != null && dp.size() == 2)
            {
                COSBase dashArray = dp.getObject(0);
                COSBase phase = dp.getObject(1);
                if (dashArray instanceof COSArray && phase instanceof COSNumber)
                {
                    retval = new PDLineDashPattern((COSArray) dashArray, ((COSNumber) phase).intValue());
                }
            }
            return retval;
        }

        /**
         * This will set the dash pattern for the graphics state.
         *
         * @param dashPattern The dash pattern
         */
        public void setLineDashPattern( PDLineDashPattern dashPattern )
        {
            dict.setItem(COSName.D, dashPattern.getCOSObject());
        }

        /**
         * This will get the rendering intent.
         *
         * @return null or the RI value in the dictionary.
         */
        public RenderingIntent getRenderingIntent()
        {
            String ri = dict.getNameAsString( "RI" );
            if (ri != null)
            {
                return RenderingIntent.fromString(ri);
            }
            else
            {
                return null;
            }
        }

        /**
         * This will set the rendering intent for the graphics state.
         *
         * @param ri The new rendering intent
         */
        public void setRenderingIntent( String ri )
        {
            dict.setName("RI", ri);
        }

        /**
         * This will get the overprint control.
         *
         * @return The overprint control or null if one has not been set.
         */
        public boolean getStrokingOverprintControl()
        {
            return dict.getBoolean(COSName.OP, false);
        }

        /**
         * This will set the overprint control(OP).
         *
         * @param op The overprint control.
         */
        public void setStrokingOverprintControl( boolean op )
        {
            dict.setBoolean(COSName.OP, op);
        }

        /**
         * This will get the overprint control for non stroking operations.  If this
         * value is null then the regular overprint control value will be returned.
         *
         * @return The overprint control or null if one has not been set.
         */
        public boolean getNonStrokingOverprintControl()
        {
            return dict.getBoolean( COSName.OP_NS, getStrokingOverprintControl() );
        }

        /**
         * This will set the overprint control(OP).
         *
         * @param op The overprint control.
         */
        public void setNonStrokingOverprintControl( boolean op )
        {
            dict.setBoolean(COSName.OP_NS, op);
        }

        /**
         * This will get the overprint control mode.
         *
         * @return The overprint control mode or null if one has not been set.
         */
        public Integer getOverprintMode()
        {
            Integer retval = null;
            COSBase base = dict.getDictionaryObject(COSName.OPM);
            if (base instanceof COSNumber)
            {
                COSNumber value = (COSNumber) base;
                retval = value.intValue();
            }
            return retval;
        }

        /**
         * This will set the overprint mode(OPM).
         *
         * @param overprintMode The overprint mode
         */
        public void setOverprintMode(Integer overprintMode)
        {
            if (overprintMode == null)
            {
                dict.removeItem(COSName.OPM);
            }
            else
            {
                dict.setInt(COSName.OPM, overprintMode);
            }
        }

        /**
         * This will get the font setting of the graphics state.
         *
         * @return The font setting.
         */
        public PDFontSetting getFontSetting()
        {
            COSArray font = dict.getCOSArray(COSName.FONT);
            return font != null ? new PDFontSetting(font) : null;
        }

        /**
         * This will set the font setting for this graphics state.
         *
         * @param fs The new font setting.
         */
        public void setFontSetting( PDFontSetting fs )
        {
            dict.setItem(COSName.FONT, fs);
        }

        /**
         * This will get the flatness tolerance.
         *
         * @return The flatness tolerance or null if one has not been set.
         */
        public Float getFlatnessTolerance()
        {
            return getFloatItem( COSName.FL );
        }

        /**
         * This will set the flatness tolerance.
         *
         * @param flatness The new flatness tolerance
         */
        public void setFlatnessTolerance( Float flatness )
        {
            setFloatItem(COSName.FL, flatness);
        }

        /**
         * This will get the smothness tolerance.
         *
         * @return The smothness tolerance or null if one has not been set.
         */
        public Float getSmoothnessTolerance()
        {
            return getFloatItem( COSName.SM );
        }

        /**
         * This will set the smoothness tolerance.
         *
         * @param smoothness The new smoothness tolerance
         */
        public void setSmoothnessTolerance( Float smoothness )
        {
            setFloatItem( COSName.SM, smoothness );
        }

        /**
         * This will get the automatic stroke adjustment flag.
         *
         * @return The automatic stroke adjustment flag or null if one has not been set.
         */
        public boolean getAutomaticStrokeAdjustment()
        {
            return dict.getBoolean(COSName.SA, false);
        }

        /**
         * This will set the automatic stroke adjustment flag.
         *
         * @param sa The new automatic stroke adjustment flag.
         */
        public void setAutomaticStrokeAdjustment( boolean sa )
        {
            dict.setBoolean(COSName.SA, sa);
        }

        /**
         * This will get the stroking alpha constant.
         *
         * @return The stroking alpha constant or null if one has not been set.
         */
        public Float getStrokingAlphaConstant()
        {
            return getFloatItem(COSName.CA);
        }

        /**
         * This will set the stroking alpha constant.
         *
         * @param alpha The new stroking alpha constant.
         */
        public void setStrokingAlphaConstant( Float alpha )
        {
            setFloatItem(COSName.CA, alpha);
        }

        /**
         * This will get the non stroking alpha constant.
         *
         * @return The non stroking alpha constant or null if one has not been set.
         */
        public Float getNonStrokingAlphaConstant()
        {
            return getFloatItem( COSName.CA_NS );
        }

        /**
         * This will set the non stroking alpha constant.
         *
         * @param alpha The new non stroking alpha constant.
         */
        public void setNonStrokingAlphaConstant( Float alpha )
        {
            setFloatItem( COSName.CA_NS, alpha );
        }

        /**
         * This will get the alpha source flag (“alpha is shape”), that specifies whether the current
         * soft mask and alpha constant shall be interpreted as shape values (true) or opacity values
         * (false).
         *
         * @return The alpha source flag.
         */
        public boolean getAlphaSourceFlag()
        {
            return dict.getBoolean(COSName.AIS, false);
        }

        /**
         * This will set the alpha source flag (“alpha is shape”), that specifies whether the current
         * soft mask and alpha constant shall be interpreted as shape values (true) or opacity values
         * (false).
         *
         * @param alpha The alpha source flag.
         */
        public void setAlphaSourceFlag( boolean alpha )
        {
            dict.setBoolean(COSName.AIS, alpha);
        }

        /**
         * Returns the blending mode stored in the COS dictionary
         *
         * @return the blending mode
         */
        public BlendMode getBlendMode()
        {
            return BlendMode.getInstance(dict.getDictionaryObject(COSName.BM));
        }

        /**
         * Set the blending mode.
         *
         * @param bm
         */
        public void setBlendMode(BlendMode bm)
        {
            dict.setItem(COSName.BM, bm.getCOSName());
        }

        /**
         * Returns the soft mask stored in the COS dictionary
         *
         * @return the soft mask or null if there isn't any.
         */
        public PDSoftMask getSoftMask()
        {
            if (!dict.containsKey(COSName.SMASK))
            {
                return null;
            }
            return PDSoftMask.create(dict.getDictionaryObject(COSName.SMASK));
        }

        /**

        /**
         * This will get the text knockout flag.
         *
         * @return The text knockout flag.
         */
        public boolean getTextKnockoutFlag()
        {
            return dict.getBoolean( COSName.TK,true );
        }

        /**
         * This will set the text knockout flag.
         *
         * @param tk The text knockout flag.
         */
        public void setTextKnockoutFlag( boolean tk )
        {
            dict.setBoolean(COSName.TK, tk);
        }

        /**
         * This will get a float item from the dictionary.
         *
         * @param key The key to the item.
         *
         * @return The value for that item.
         */
        private Float getFloatItem( COSName key )
        {
            Float retval = null;

            COSBase base = dict.getDictionaryObject(key);
            if (base instanceof COSNumber)
            {
                COSNumber value = (COSNumber) base;
                retval = value.floatValue();
            }
            return retval;
        }

        /**
         * This will set a float object.
         *
         * @param key The key to the data that we are setting.
         * @param value The value that we are setting.
         */
        private void setFloatItem( COSName key, Float value )
        {
            if( value == null )
            {
                dict.removeItem(key);
            }
            else
            {
                dict.setItem(key, new COSFloat(value));
            }
        }

        /**
         * This will get the transfer function of the /TR dictionary.
         *
         * @return The transfer function. According to the PDF specification, this is either a single
         * function (which applies to all process colorants) or an array of four functions (which apply
         * to the process colorants individually). The name Identity may be used to represent the
         * identity function.
         */
        public COSBase getTransfer()
        {
            COSBase base = dict.getDictionaryObject(COSName.TR);
            if (base instanceof COSArray && ((COSArray) base).size() != 4)
            {
                return null;
            }
            return base;
        }

        /**
         * This will set the transfer function of the /TR dictionary.
         *
         * @param transfer The transfer function. According to the PDF specification, this is either a
         * single function (which applies to all process colorants) or an array of four functions (which
         * apply to the process colorants individually). The name Identity may be used to represent the
         * identity function.
         */
        public void setTransfer(COSBase transfer)
        {
            dict.setItem(COSName.TR, transfer);
        }

        /**
         * This will get the transfer function of the /TR2 dictionary.
         *
         * @return The transfer function. According to the PDF specification, this is either a single
         * function (which applies to all process colorants) or an array of four functions (which apply
         * to the process colorants individually). The name Identity may be used to represent the
         * identity function, and the name Default denotes the transfer function that was in effect at
         * the start of the page.
         */
        public COSBase getTransfer2()
        {
            COSBase base = dict.getDictionaryObject(COSName.TR2);
            if (base instanceof COSArray && ((COSArray) base).size() != 4)
            {
                return null;
            }
            return base;
        }

        /**
         * This will set the transfer function of the /TR2 dictionary.
         *
         * @param transfer2 The transfer function. According to the PDF specification, this is either a
         * single function (which applies to all process colorants) or an array of four functions (which
         * apply to the process colorants individually). The name Identity may be used to represent the
         * identity function, and the name Default denotes the transfer function that was in effect at
         * the start of the page.
         */
        public void setTransfer2(COSBase transfer2)
        {
            dict.setItem(COSName.TR2, transfer2);
        }
    }