# COSFloat.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.cos;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.math.BigDecimal;
    import java.nio.charset.StandardCharsets;

    /**
     * This class represents a floating point number in a PDF document.
     *
     * @author Ben Litchfield
     *
     */
    public class COSFloat extends COSNumber
    {
        private Float value;
        private String valueAsString;

        /**
         * Constructor.
         *
         * @param aFloat The primitive float object that this object wraps.
         */
        public COSFloat( float aFloat )
        {
            value = aFloat;
        }

        /**
         * Constructor.
         *
         * @param aFloat The primitive float object that this object wraps.
         *
         * @throws IOException If aFloat is not a float.
         */
        public COSFloat( String aFloat ) throws IOException
        {
            try
            {
                value = Float.parseFloat(aFloat);
                valueAsString = checkMinMaxValues() ? null : aFloat;
            }
            catch( NumberFormatException e )
            {
                if (aFloat.startsWith("--"))
                {
                    // PDFBOX-4289 has --16.33
                    aFloat = aFloat.substring(1);
                }
                else if (aFloat.matches("^0\\.0*-\\d+"))
                {
                    // PDFBOX-2990 has 0.00000-33917698
                    // PDFBOX-3369 has 0.00-35095424
                    // PDFBOX-3500 has 0.-262
                    aFloat = "-" + aFloat.replaceFirst("-", "");
                }
                else
                {
                    throw new IOException("Error expected floating point number actual='" + aFloat + "'", e);
                }

                try
                {
                    value = Float.parseFloat(aFloat);
                    checkMinMaxValues();
                }
                catch (NumberFormatException e2)
                {
                    throw new IOException("Error expected floating point number actual='" + aFloat + "'", e2);
                }
            }

        }

        /**
         * Check and coerce the value field to be between MIN_NORMAL and MAX_VALUE. Returns "true" if the value was
         * replaced.
         *
         * @return true if the value was replaced
         */
        private boolean checkMinMaxValues()
        {
            if (value == Float.POSITIVE_INFINITY)
            {
                value = Float.MAX_VALUE;
            }
            else if (value == Float.NEGATIVE_INFINITY)
            {
                value = -Float.MAX_VALUE;
            }
            else if (Math.abs(value) < Float.MIN_NORMAL)
            {
                // values smaller than the smallest possible float value are converted to 0
                // see PDF spec, chapter 2 of Appendix C Implementation Limits
                value = 0f;
            }
            else
            {
                return false;
            }
            return true;
        }

        /**
         * If the string represents a floating point number, this will remove all trailing zeros
         *
         * @param plainStringValue a decimal number
         */
        private String trimZeros(String plainStringValue)
        {
            int lastIndex = plainStringValue.lastIndexOf('.');
            if (lastIndex > 0)
            {
                int i = plainStringValue.length() - 1;
                while (i > lastIndex + 1 && plainStringValue.charAt(i) == '0')
                {
                    i--;
                }
                return plainStringValue.substring(0, i + 1);
            }
            return plainStringValue;
        }

        /**
         * The value of the float object that this one wraps.
         *
         * @return The value of this object.
         */
        @Override
        public float floatValue()
        {
            return value;
        }

        /**
         * This will get the long value of this object.
         *
         * @return The long value of this object,
         */
        @Override
        public long longValue()
        {
            return value.longValue();
        }

        /**
         * This will get the integer value of this object.
         *
         * @return The int value of this object,
         */
        @Override
        public int intValue()
        {
            return value.intValue();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals( Object o )
        {
            return o instanceof COSFloat &&
                    Float.floatToIntBits(((COSFloat)o).value) == Float.floatToIntBits(value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode()
        {
            return value.hashCode();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString()
        {
            return "COSFloat{" + formatString() + "}";
        }

        /**
         * Builds, if needed, and returns the string representation of the current value.
         * @return current value as string.
         */
        private String formatString()
        {
            if (valueAsString == null)
            {
                valueAsString = trimZeros(new BigDecimal(String.valueOf(value)).toPlainString());
            }
            return valueAsString;
        }

        /**
         * Visitor pattern double dispatch method.
         *
         * @param visitor The object to notify when visiting this object.
         * @throws IOException If an error occurs while visiting this object.
         */
        @Override
        public void accept(ICOSVisitor visitor) throws IOException
        {
            visitor.visitFromFloat(this);
        }

        /**
         * This will output this string as a PDF object.
         *
         * @param output The stream to write to.
         * @throws IOException If there is an error writing to the stream.
         */
        public void writePDF( OutputStream output ) throws IOException
        {
            output.write(formatString().getBytes(StandardCharsets.ISO_8859_1));
        }
    }