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

org.jpedal.objects.acroforms.creation.AnnotationFactory Maven / Gradle / Ivy

The newest version!
/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
 @LICENSE@
 *
 * ---------------
 * AnnotationFactory.java
 * ---------------
 */
package org.jpedal.objects.acroforms.creation;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import javax.imageio.ImageIO;

import org.jpedal.color.DeviceCMYKColorSpace;
import org.jpedal.fonts.FontMappings;
import org.jpedal.fonts.StandardFonts;
import org.jpedal.objects.GraphicsState;

import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.PdfArrayIterator;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.LogWriter;

public class AnnotationFactory {

    /**
     * Determine the type of annotation from the sub type value and call
     * appropriate method to create an icon for the annotation
     *
     * @param form PdfObject containing the annotation
     * @return BufferedImage of the annotation or null if the supplied FormObject contains errors
     */
    public static BufferedImage getIcon(final PdfObject form) {
        return getIcon(form, 1.0f);
    }

    /**
     * Determine the type of annotation from the sub type value and call
     * appropriate method to create an icon for the annotation with the given
     * scaling applied
     *
     * @param form    PdfObject containing the annotation
     * @param scaling Scaling to display the annotation at
     * @return BufferedImage of the annotation or null if the supplied FormObject contains errors
     */
    public static BufferedImage getIcon(final PdfObject form, final float scaling) {

        switch (form.getParameterConstant(PdfDictionary.Subtype)) {
            case PdfDictionary.Text:
                return getTextIcon(form);
            case PdfDictionary.Highlight:
                return getHightlightIcon(form, scaling);
            case PdfDictionary.Square:
                return getSquareIcon(form, scaling);
            case PdfDictionary.Underline:
                return getUnderLineIcon(form);
            case PdfDictionary.StrickOut:
                return getStrickOutIcon(form);
            case PdfDictionary.Caret:
                return getCaretIcon(form);
            case PdfDictionary.FileAttachment:
                return getFileAttachmentIcon();
            case PdfDictionary.Line:
                return getLineIcon(form, scaling);
            case PdfDictionary.Polygon:
                return getPolyIcon(form, false, scaling);
            case PdfDictionary.PolyLine:
                return getPolyIcon(form, true, scaling);
            case PdfDictionary.Circle:
                return getCircleIcon(form, scaling);
            case PdfDictionary.Squiggly:
                return getSquigglyIcon(form);
            case PdfDictionary.Sound:
                return getSoundIcon(form);
            case PdfDictionary.Ink:
                return getInkIcon((FormObject) form, scaling);
        }

        return null;
    }

    private static BufferedImage getInkIcon(final FormObject form, final float scaling) {
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        if (quad != null) {

            final Rectangle bounds = getFormBounds(form, quad, scaling);
            final float[][] InkListArray = form.getFloat2DArray(PdfDictionary.InkList);
            if (bounds.width > 0 && bounds.height > 0) {
                final BufferedImage icon1 = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g2 = (Graphics2D) icon1.getGraphics();
                setStroke(form, g2, scaling);
                scanInkListFloatTree(InkListArray, form, g2, scaling);
                return icon1;
            }
        }
        return null;
    }


    /**
     * Method to scan through an InkList array, draw the ink to the provided
     * Graphics object and return the bounds of the InkList
     *
     * @param InkListArray Object array representing an InkList
     * @param form         FormObject for the Ink annotation this InkList came from
     * @param g            Graphics object to draw the Ink to
     * @return float array representing the Inks bounds in the order
     * lowest x / y, largest x / x
     */
    @SuppressWarnings("UnusedDeclaration")
    public static float[] scanInkListTree(final Object[] InkListArray, final PdfObject form, final Graphics g) {
        return scanInkListTree(InkListArray, form, g, 1.0f);
    }

    /**
     * Method to scan through an InkList array, draw the ink to the provided
     * Graphics object at the specified scaling and return the bounds of the InkList
     *
     * @param InkListArray Object array representing an InkList
     * @param form         FormObject representing the Ink annotation this InkList came from
     * @param g            Graphics object to draw the Ink to
     * @param scaling      float value representing scaling where 1 equals 100%
     * @return float array representing the Inks bounds in the order
     * lowest x / y, largest x / x
     */
    public static float[] scanInkListTree(final Object[] InkListArray, final PdfObject form, final Graphics g, final float scaling) {

        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        if (quad == null) {
            return null;
        }

        final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);

        float minX = bounds.x;
        float minY = bounds.y;
        float maxX = bounds.x + bounds.width;
        float maxY = bounds.y + bounds.height;

        float[] vals = null;
        final Graphics2D g2 = (Graphics2D) g;

        setStroke(form, g2, scaling);

        //if specific DecodeParms for each filter, set othereise use global
        if (InkListArray != null) {

            final int count = InkListArray.length;

            float x;
            float y;

            //If Graphics not set, don't draw anything.
            if (g != null) {
                final float[] underlineColor = form.getFloatArray(PdfDictionary.C);
                Color c1 = new Color(0);
                if (underlineColor != null) {
                    switch (underlineColor.length) {
                        case 0:
                            //Should not happen. Do nothing. Annotation is transparent
                            break;
                        case 1:
                            //DeviceGrey colorspace
                            c1 = new Color(underlineColor[0], underlineColor[0], underlineColor[0], 1.0f);
                            break;
                        case 3:
                            //DeviceRGB colorspace
                            c1 = new Color(underlineColor[0], underlineColor[1], underlineColor[2], 1.0f);
                            break;
                        case 4:
                            //DeviceCMYK colorspace
                            final DeviceCMYKColorSpace cmyk = new DeviceCMYKColorSpace();
                            cmyk.setColor(underlineColor, 4);
                            c1 = new Color(cmyk.getColor().getRGB());

                            break;
                        default:
                            break;
                    }
                }

                g2.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
                g2.fillRect(0, 0, bounds.width, bounds.height);
                g2.setColor(c1);
                g2.setPaint(c1);
            }

            for (int i = 0; i < count; i++) {

                if (InkListArray[i] instanceof byte[]) {
                    final byte[] decodeByteData = (byte[]) InkListArray[i];

                    if (vals == null) {
                        vals = new float[count];
                    }

                    if (decodeByteData != null) {
                        final String val = new String(decodeByteData);
                        final float v = Float.parseFloat(val) * scaling;

                        switch (i % 2) {
                            case 0:
                                if (v < minX) {
                                    minX = v;
                                }
                                if (v > maxX) {
                                    maxX = v;
                                }
                                x = (v - bounds.x);
                                vals[i] = x;
                                break;
                            case 1:
                                if (v < minY) {
                                    minY = v;
                                }
                                if (v > maxY) {
                                    maxY = v;
                                }
                                y = bounds.height - (v - bounds.y);
                                vals[i] = y;

                                break;
                        }
                    }
                } else {
                    final float[] r = scanInkListTree((Object[]) InkListArray[i], form, g, scaling);
                    if (r[0] < minX) {
                        minX = r[0];
                    }
                    if (r[2] > maxX) {
                        maxX = r[2];
                    }
                    if (r[1] < minY) {
                        minY = r[1];
                    }
                    if (r[3] > maxY) {
                        maxY = r[3];
                    }
                }
            }
        }

        if (vals != null) {
            if (vals.length < 4) {
                if (g2 != null) {

                    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                    final Line2D.Float line = new Line2D.Float(vals[0], vals[1], vals[0], vals[1]);
                    g2.draw(line);
                }
            } else {
                if (vals.length < 6) { //Only use lines on ink
                    if (g2 != null) {

                        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                        for (int i = 0; i < vals.length; i += 4) {
                            final Line2D.Float line = new Line2D.Float(vals[0], vals[1], vals[2], vals[3]);
                            g2.draw(line);
                        }
                    }
                } else { //Enough armguments so curve ink
                    final float[] values = createInkCurve(vals);
                    if (g2 != null) {

                        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                        for (int i = 0; i < values.length - 7; i += 6) {

                            final CubicCurve2D curve = new CubicCurve2D.Double(values[i], values[i + 1], values[i + 2], values[i + 3], values[i + 4], values[i + 5], values[i + 6], values[i + 7]);
                            g2.draw(curve);
                        }
                    }
                }
            }
        }

        return new float[]{minX, minY, maxX, maxY};
    }


    /**
     * Method to scan through an InkList array, draw the ink to the provided
     * Graphics object at the specified scaling and return the bounds of the InkList
     *
     * @param InkListArray 2 Dimensional float array representing an InkList
     * @param form         FormObject representing the Ink annotation this InkList came from
     * @param g            Graphics object to draw the Ink to
     * @param scaling      float value representing scaling where 1 equals 100%
     * @return float array representing the Inks bounds in the order
     * lowest x / y, largest x / x
     */
    public static float[] scanInkListFloatTree(final float[][] InkListArray, final PdfObject form, final Graphics g, final float scaling) {

        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        if (quad == null) {
            return null;
        }

        Rectangle2D bounds = getFormBounds((FormObject) form, quad, scaling);

        final Graphics2D g2 = (Graphics2D) g;

        setStroke(form, g2, scaling);

        //if specific DecodeParms for each filter, set othereise use global
        if (InkListArray != null) {

            //If Graphics not set, don't draw anything.
            if (g != null) {
                final float[] underlineColor = form.getFloatArray(PdfDictionary.C);
                Color c1 = new Color(0);
                if (underlineColor != null) {
                    switch (underlineColor.length) {
                        case 0:
                            //Should not happen. Do nothing. Annotation is transparent
                            break;
                        case 1:
                            //DeviceGrey colorspace
                            c1 = new Color(underlineColor[0], underlineColor[0], underlineColor[0], 1.0f);
                            break;
                        case 3:
                            //DeviceRGB colorspace
                            c1 = new Color(underlineColor[0], underlineColor[1], underlineColor[2], 1.0f);
                            break;
                        case 4:
                            //DeviceCMYK colorspace
                            final DeviceCMYKColorSpace cmyk = new DeviceCMYKColorSpace();
                            cmyk.setColor(underlineColor, 4);
                            c1 = new Color(cmyk.getColor().getRGB());

                            break;
                        default:
                            break;
                    }
                }

                g2.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
                g2.fillRect(0, 0, (int) bounds.getWidth(), (int) bounds.getHeight());
                g2.setColor(c1);
                g2.setPaint(c1);
            }

            for (final float[] coords : InkListArray) {

                if (coords.length < 4) {
                    if (g2 != null) {

                        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                        final Line2D.Float line = new Line2D.Float(coords[0], coords[1], coords[0], coords[1]);
                        g2.draw(line);
                    }
                } else {
                    if (coords.length < 6) { //Only use lines on ink
                        if (g2 != null) {

                            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                            for (int i = 0; i < coords.length; i += 4) {
                                final Line2D.Float line = new Line2D.Float(coords[0], coords[1], coords[2], coords[3]);
                                g2.draw(line);
                            }
                        }
                    } else { //Enough armguments so curve ink
                        final float[] values = createInkCurve(coords);
                        if (g2 != null) {

                            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                            for (int i = 0; i < values.length - 7; i += 6) {

                                final CubicCurve2D curve = new CubicCurve2D.Double(values[i], values[i + 1], values[i + 2], values[i + 3], values[i + 4], values[i + 5], values[i + 6], values[i + 7]);
                                g2.draw(curve);
                                bounds = bounds.createUnion(curve.getBounds2D());
                            }
                        }
                    }
                }
            }
        }

        form.setFloatArray(PdfDictionary.Rect, new float[]{(float) bounds.getMinX(), (float) bounds.getMinY(), (float) bounds.getMaxX(), (float) bounds.getMaxY()});

        return form.getFloatArray(PdfDictionary.Rect);
    }

    private static Color convertFloatArrayToColor(final float[] values) {
        Color c = new Color(0, 0, 0, 0);
        if (values != null) {
            switch (values.length) {
                case 0:
                    //Should not happen. Do nothing. Annotation is transparent
                    break;
                case 1:
                    //DeviceGrey colorspace
                    c = new Color(values[0], values[0], values[0]);
                    break;
                case 3:
                    //DeviceRGB colorspace
                    c = new Color(values[0], values[1], values[2]);
                    break;
                case 4:
                    //DeviceCMYK colorspace
                    final DeviceCMYKColorSpace cmyk = new DeviceCMYKColorSpace();
                    cmyk.setColor(values, 4);
                    c = new Color(cmyk.getColor().getRGB());
                    c = new Color(c.getRed(), c.getGreen(), c.getBlue());

                    break;
                default:
                    break;
            }
        }
        return c;
    }

    private static Rectangle getFormBounds(final FormObject form, final float[] rect, final float scaling) {
        final Rectangle bounds = (form).getBoundingRectangle();

        //Bounds is 0 so calculate based on rect areas
        if (bounds.getWidth() == 0 && bounds.getHeight() == 0) {
            for (int i = 0; i != rect.length; i++) {
                if (i % 2 == 0) {
                    if (bounds.x > rect[i]) {
                        bounds.x = (int) rect[i];
                    }
                    if (bounds.x + bounds.width < rect[i]) {
                        bounds.width = (int) (rect[i] - bounds.x);
                    }
                } else {
                    if (bounds.y > rect[i]) {
                        bounds.y = (int) rect[i];
                    }
                    if (bounds.y + bounds.height < rect[i]) {
                        bounds.height = (int) (rect[i] - bounds.y);
                    }
                }

            }
        }

        bounds.x = (int) ((bounds.x * scaling) + 0.5f);
        bounds.y = (int) ((bounds.y * scaling) + 0.5f);
        bounds.width = (int) ((bounds.width * scaling) + 0.5f);
        bounds.height = (int) ((bounds.height * scaling) + 0.5f);

        return bounds;
    }

    private static BufferedImage getStrickOutIcon(final PdfObject form) {

        final Color color = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));

        float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad == null) {
            quad = form.getFloatArray(PdfDictionary.Rect);
        }

        final Rectangle bounds = getFormBounds((FormObject) form, quad, 1.0f);
        if (bounds.width > 0 && bounds.height > 0) {
            final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics g = icon.getGraphics();

            if (quad.length >= 8) {
                for (int hi = 0; hi != quad.length; hi += 8) {
                    final int x = (int) quad[hi] - bounds.x;
                    int y = (int) quad[hi + 5] - bounds.y;
                    //Adjust y for display
                    y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                    final int width = (int) (quad[hi + 2] - quad[hi]);
                    final int height = (int) (quad[hi + 1] - quad[hi + 5]);

                    try {
                        g.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
                        g.fillRect(0, 0, width, height);
                        g.setColor(color);
                        g.fillRect(x, y + (height / 2), width, 1);
                    } catch (final Exception e) {
                        LogWriter.writeLog("Exception: " + e.getMessage());
                    }
                }
            }
            return icon;
        }
        return null;
    }

    private static BufferedImage getUnderLineIcon(final PdfObject form) {

        final Color color = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));

        float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad == null) {
            quad = form.getFloatArray(PdfDictionary.Rect);
        }

        final Rectangle bounds = getFormBounds((FormObject) form, quad, 1.0f);
        if (bounds.width > 0 && bounds.height > 0) {
            final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics g = icon.getGraphics();

            if (quad.length >= 8) {
                for (int hi = 0; hi != quad.length; hi += 8) {
                    final int x = (int) quad[hi] - bounds.x;
                    int y = (int) quad[hi + 5] - bounds.y;
                    //Adjust y for display
                    y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                    final int width = (int) (quad[hi + 2] - quad[hi]);
                    final int height = (int) (quad[hi + 1] - quad[hi + 5]);

                    try {
                        g.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
                        g.fillRect(x, y, width, height);
                        g.setColor(color);
                        g.fillRect(x, y + height - 1, width, 1);
                    } catch (final Exception e) {
                        LogWriter.writeLog("Exception: " + e.getMessage());
                    }
                }
            }

            return icon;
        }

        return null;
    }

    private static BufferedImage getSquigglyIcon(final PdfObject form) {

        final Color color = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));

        float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad == null) {
            quad = form.getFloatArray(PdfDictionary.Rect);
        }

        final Rectangle bounds = getFormBounds((FormObject) form, quad, 1.0f);
        if (bounds.width > 0 && bounds.height > 0) {
            final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics g = icon.getGraphics();

            if (quad.length >= 8) {
                for (int hi = 0; hi != quad.length; hi += 8) {
                    final int x = (int) quad[hi] - bounds.x;
                    int y = (int) quad[hi + 5] - bounds.y;
                    //Adjust y for display
                    y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                    final int width = (int) (quad[hi + 2] - quad[hi]);
                    final int height = (int) (quad[hi + 1] - quad[hi + 5]);
                    final int step = 6;
                    final int bottom = y + height - 1;
                    final int top = bottom - (step / 2);
                    try {
                        g.setColor(color);

                        for (int i = 0; i < width; i += step) {
                            g.drawLine(x + i, bottom, x + i + (step / 2), top);
                            g.drawLine(x + i + (step / 2), top, x + i + step, bottom);
                        }
                    } catch (final Exception e) {
                        LogWriter.writeLog("Exception: " + e.getMessage());
                    }
                }
            }

            return icon;
        }

        return null;
    }

    private static BufferedImage getSquareIcon(final PdfObject form, final float scaling) {
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final Color ic = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.IC));

        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        if (quad != null) {

            final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);
            if (bounds.width > 0 && bounds.height > 0) {
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D) icon.getGraphics();
                final int width = setStroke(form, g, scaling);
                g.setColor(ic);
                g.fillRect(0, 0, bounds.width, bounds.height);
                g.setColor(c);
                g.drawRect(width / 2, width / 2, bounds.width - width, bounds.height - width);
                //            FormRenderUtilsG2.renderBorder(g, (FormObject)form, 0,0,icon.getWidth(), icon.getHeight());

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }

    private static int setStroke(final PdfObject form, final Graphics2D g, final float scaling) {
        int borderWidth = 1;
        final PdfObject BS = form.getDictionary(PdfDictionary.BS);
        if (BS != null && g != null) {
            final String s = BS.getName(PdfDictionary.S);
            borderWidth = BS.getInt(PdfDictionary.W);
            if (borderWidth == -1) {
                borderWidth = 1;
            }
            final PdfArrayIterator d = BS.getMixedArray(PdfDictionary.D);

            if (s == null || s.equals("S")) {
                borderWidth *= scaling;
                g.setStroke(new BasicStroke(borderWidth));
            } else {
                if (s.equals("D")) {
                    float[] dash = {3};
                    if (d != null && d.hasMoreTokens()) {
                        final int count = d.getTokenCount();
                        if (count > 0) {
                            dash = d.getNextValueAsFloatArray();
                        }
                    }
                    borderWidth *= scaling;
                    g.setStroke(new BasicStroke(borderWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, dash, 0));
                }
            }
        }

        if (borderWidth < 1) {
            borderWidth = 1;
        }

        return borderWidth;
    }

    private static BufferedImage getCircleIcon(final PdfObject form, final float scaling) {
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final Color ic = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.IC));
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);

        if (quad != null) {

            final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);
            if (bounds.width > 0 && bounds.height > 0) {
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D) icon.getGraphics();

                final int width = setStroke(form, g, scaling);

                g.setColor(ic);
                g.fillOval((width / 2), (width / 2), bounds.width - width, bounds.height - width);
                g.setColor(c);
                g.drawOval((width / 2), (width / 2), bounds.width - width, bounds.height - width);

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }

    private static BufferedImage getLineIcon(final PdfObject form, final float scaling) {
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));

        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        final float[] line = form.getFloatArray(PdfDictionary.L);
        if (quad != null && line != null) {

            final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);
            if (bounds.width > 0 && bounds.height > 0) {
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D) icon.getGraphics();
                setStroke(form, g, scaling);
                g.setColor(c);
                g.drawLine((int) (line[0] * scaling) - bounds.x, (int) (bounds.height - ((line[1] * scaling) - bounds.y)), (int) (line[2] * scaling) - bounds.x, (int) (bounds.height - ((line[3] * scaling) - bounds.y)));

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }

    private static BufferedImage getPolyIcon(final PdfObject form, final boolean line, final float scaling) {
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final Color ic = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.IC));

        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        final float[] vertices = form.getFloatArray(PdfDictionary.Vertices);

        if (quad != null && vertices != null) {

            final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);
            if (bounds.width > 0 && bounds.height > 0) {
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D) icon.getGraphics();
                setStroke(form, g, scaling);

                final GeneralPath gPath = new GeneralPath(Path2D.WIND_NON_ZERO);
                gPath.moveTo((int) (vertices[0] * scaling) - bounds.x, (int) (bounds.height - ((vertices[1] * scaling) - bounds.y)));

                for (int i = 2; i != vertices.length; i += 2) {
                    gPath.lineTo((int) (vertices[i] * scaling) - bounds.x, (int) (bounds.height - ((vertices[i + 1] * scaling) - bounds.y)));
                }

                if (!line) {
                    gPath.lineTo((int) (vertices[0] * scaling) - bounds.x, (int) (bounds.height - ((vertices[1] * scaling) - bounds.y)));
                    g.setColor(ic);
                    g.fill(gPath);
                }

                g.setColor(c);
                g.draw(gPath);

                return icon;
            }
        }

        //Return a small empty image as no highlight to make.
        return null;
    }

    private static BufferedImage getCaretIcon(final PdfObject form) {
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final float[] rd = form.getFloatArray(PdfDictionary.RD);
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);

        if (quad != null) {

            final Rectangle bounds = getFormBounds((FormObject) form, quad, 1.0f);
            if (bounds.width > 0 && bounds.height > 0) {
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D) icon.getGraphics();
                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                g.setStroke(new BasicStroke(rd[1]));
                g.setColor(c);
                g.drawLine(0, bounds.height, bounds.width / 2, 0);
                g.drawLine(bounds.width / 2, 0, bounds.width, bounds.height);

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }

    private static BufferedImage getHightlightIcon(final PdfObject form, final float scaling) {
        final float[] f = form.getFloatArray(PdfDictionary.C);
        Color c = new Color(0);
        if (f != null) {
            switch (f.length) {
                case 0:
                    //Should not happen. Do nothing. Annotation is transparent
                    break;
                case 1:
                    //DeviceGrey colorspace
                    c = new Color(f[0], f[0], f[0], 0.5f);
                    break;
                case 3:
                    //DeviceRGB colorspace
                    c = new Color(f[0], f[1], f[2], 0.5f);
                    break;
                case 4:
                    //DeviceCMYK colorspace
                    final DeviceCMYKColorSpace cmyk = new DeviceCMYKColorSpace();
                    cmyk.setColor(f, 4);
                    c = new Color(cmyk.getColor().getRGB());
                    c = new Color(c.getRed(), c.getGreen(), c.getBlue(), 0.5f);

                    break;
                default:
                    break;
            }
        }

        final float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad != null) {
            final Rectangle bounds = ((FormObject) form).getBoundingRectangle();

            //Bounds is 0 so calculate based on quad areas
            if (bounds.getWidth() == 0 && bounds.getHeight() == 0) {
                for (int i = 0; i != quad.length; i++) {
                    if (i % 2 == 0) {
                        if (bounds.x > quad[i]) {
                            bounds.x = (int) quad[i];
                        }
                        if (bounds.x + bounds.width < quad[i]) {
                            bounds.width = (int) (quad[i] - bounds.x);
                        }
                    } else {
                        if (bounds.y > quad[i]) {
                            bounds.y = (int) quad[i];
                        }
                        if (bounds.y + bounds.height < quad[i]) {
                            bounds.height = (int) (quad[i] - bounds.y);
                        }
                    }

                }
            }

            final int scaledWidth = (int) (bounds.width * scaling);
            final int scaledHeight = (int) (bounds.height * scaling);
            if (scaledWidth > 0 && scaledHeight * scaling > 0) {
                final BufferedImage icon = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics g = icon.getGraphics();

                if (quad.length >= 8) {
                    for (int hi = 0; hi != quad.length; hi += 8) {

                        float x1 = quad[hi];
                        float x2 = quad[hi + 2];
                        float y1 = quad[hi + 1];
                        float y2 = quad[hi + 3];
                        for (int j = 0; j < 8; j++) {
                            // If even
                            if (j % 2 == 0) {
                                if (quad[hi + j] < x1) {
                                    x1 = quad[hi + j];
                                }
                                if (quad[hi + j] > x2) {
                                    x2 = quad[hi + j];
                                }
                            } else {
                                if (quad[hi + j] < y1) {
                                    y1 = quad[hi + j];
                                }
                                if (quad[hi + j] > y2) {
                                    y2 = quad[hi + j];
                                }
                            }
                        }

                        final int x = (int) x1 - bounds.x;
                        int y = (int) y1 - bounds.y;
                        // Adjust y for display
                        y = (bounds.height - y) - (int) (y2 - y1);
                        final int width = (int) (x2 - x1);
                        final int height = (int) (y2 - y1);
                        final Rectangle rh = new Rectangle((int) (x * scaling), (int) (y * scaling), (int) (width * scaling), (int) (height * scaling));
                        g.setColor(c);
                        g.fillRect(rh.x, rh.y, rh.width, rh.height);
                    }
                }
                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }

    /**
     * Get the Icon to be used for Text annotations
     *
     * @param form PdfObject representing a TextAnnotation
     * @return BufferedImage representing the icon for this annotation
     */
    public static BufferedImage getTextIcon(final PdfObject form) {

        final String iconFile = getPngImageForAnnotation(form);

        BufferedImage commentIcon = null;
        try {
            commentIcon = ImageIO.read(AnnotationFactory.class.getResource(iconFile));
        } catch (final IOException e) {
            LogWriter.writeLog("Exception: " + e.getMessage());
        }

        setColorForAnnotation(form, commentIcon);

        return commentIcon;
    }

    private static final int STYLE_KEY_FONT = 1718578804;
    private static final int STYLE_KEY_TEXT = 1952807028;
    private static final int STYLE_KEY_COLOR = 1668246639;

    /**
     * Load the font style described in a DS string into a Component for use by
     * some annotations (e.g FreeText annotations).
     *
     * @param DS        byte[] representing the DS string
     * @param textInput Component to be used by a given annotation
     * @param scaling   float value representing scaling where 1 is equal to 100%
     */
    public static void loadFontValues(final byte[] DS, final Component textInput, final float scaling) {

        Font font = new Font("Lucida", Font.PLAIN, 12);
        int position = 0;

        while (position < DS.length) {
            while (DS[position] == ' ') {
                position++;
            }
            final int key = (DS[position++] << 24) + (DS[position++] << 16) + (DS[position++] << 8) + (DS[position++]);

            switch (key) {
                case STYLE_KEY_FONT:
                    switch (DS[position]) {
                        case 58: //font
                            position++;
                            if (DS[position] == ' ') {
                                position++;
                            }
                            final StringBuilder name = new StringBuilder();
                            while (position < DS.length && DS[position] != ' ') {
                                name.append((char) DS[position]);
                                position++;
                            }
                            position++;
                            final StringBuilder size = new StringBuilder();
                            while (position < DS.length && (DS[position] >= '0' && DS[position] <= '9')) { //ignore decimal as we use integer in java
                                size.append((char) DS[position]);
                                position++;
                            }

                            //Progress to end
                            while (position < DS.length && DS[position] != ';') {
                                position++;
                            }
                            position++;
                            String fontName = StandardFonts.expandName(name.toString());
                            final String altName = FontMappings.fontSubstitutionAliasTable.get(fontName.toLowerCase());
                            if (altName != null) {
                                fontName = altName;
                            }
                            fontName = StandardFonts.expandName(fontName);
                            if (size.length() > 0) {
                                final int fontSize = (int) (Integer.parseInt(size.toString()) * scaling);
                                font = new Font(fontName, Font.PLAIN, fontSize);
                            }
                            break;
                        case 45: //font-stretch
                            //Do nothing for now
                            while (position < DS.length && DS[position] != ';') {
                                position++;
                            }
                            position++;
                            break;
                        default:
                            LogWriter.writeLog("Unknown style key for FreeText annotation.");
                    }
                    break;
                case STYLE_KEY_TEXT: //text-align
                    while (position < DS.length && DS[position] != ';') {
                        position++;
                    }
                    position++;
                    break;
                case STYLE_KEY_COLOR: //color as hex values
                    while (position < DS.length && DS[position] != ':') {
                        position++;
                    }
                    position++;
                    if (DS[position] == ' ') {
                        position++;
                    }

                    if (DS[position] == '#') {
                        final StringBuilder colString = new StringBuilder();
                        while (position < DS.length && DS[position] != ';') {
                            colString.append((char) DS[position]);
                            position++;
                        }
                        final Color c = new Color(
                                Integer.valueOf(colString.substring(1, 3), 16),
                                Integer.valueOf(colString.substring(3, 5), 16),
                                Integer.valueOf(colString.substring(5, 7), 16));
                        textInput.setForeground(c);
                    } else {
                        LogWriter.writeLog("Unknown color for FreeText annotation.");
                    }
                    break;
            }
        }
        textInput.setFont(font);
    }

    private static void setColorForAnnotation(final PdfObject form, final BufferedImage commentIcon) {

        //Set color of annotation
        float[] col = form.getFloatArray(PdfDictionary.C);

        if (col == null) { //If not color set we should use white
            col = new float[]{1.0f, 1.0f, 1.0f};
        }

        final Color c = new Color(col[0], col[1], col[2]);
        final int rgb = c.getRGB();

        //Replace default color with specified color
        for (int x = 0; x != commentIcon.getWidth(); x++) {
            for (int y = 0; y != commentIcon.getHeight(); y++) {

                //Checks for yellow (R255,G255,B000) and replaces with color
                if (commentIcon.getRGB(x, y) == -256) {
                    commentIcon.setRGB(x, y, rgb);
                }
            }
        }
    }

    /**
     * Get the image to be used to display an annotation based on the Name
     * variable it contains.
     * This method is intended for internal use with Text annotations only.
     *
     * @param form PdfObject representing a form Object
     * @return String value representing the location of the image to use
     */
    public static String getPngImageForAnnotation(final PdfObject form) {

        String name = form.getName(PdfDictionary.Name);

        if (name == null) {
            name = "Note";
        }
        /* Name of the icon image to use for the icon of this annotation
        * - predefined icons are needed for names:-
        * Comment, Key, Note, Help, NewParagraph, Paragraph, Insert
        */
        if (name.equals("Comment")) {
            return "/org/jpedal/objects/acroforms/res/comment.png";
        } else if (name.equals("Check")) {
            return "/org/jpedal/objects/acroforms/res/Check.png";
        } else if (name.equals("Checkmark")) {
            return "/org/jpedal/objects/acroforms/res/Checkmark.png";
        } else if (name.equals("Circle")) {
            return "/org/jpedal/objects/acroforms/res/Circle.png";
        } else if (name.equals("Cross")) {
            return "/org/jpedal/objects/acroforms/res/Cross.png";
        } else if (name.equals("CrossHairs")) {
            return "/org/jpedal/objects/acroforms/res/CrossHairs.png";
        } else if (name.equals("Help")) {
            return "/org/jpedal/objects/acroforms/res/Help.png";
        } else if (name.equals("Insert")) {
            return "/org/jpedal/objects/acroforms/res/InsertText.png";
        } else if (name.equals("Key")) {
            return "/org/jpedal/objects/acroforms/res/Key.png";
        } else if (name.equals("NewParagraph")) {
            return "/org/jpedal/objects/acroforms/res/NewParagraph.png";
        } else if (name.equals("Paragraph")) {
            return "/org/jpedal/objects/acroforms/res/Paragraph.png";
        } else if (name.equals("RightArrow")) {
            return "/org/jpedal/objects/acroforms/res/RightArrow.png";
        } else if (name.equals("RightPointer")) {
            return "/org/jpedal/objects/acroforms/res/RightPointer.png";
        } else if (name.equals("Star")) {
            return "/org/jpedal/objects/acroforms/res/Star.png";
        } else if (name.equals("UpLeftArrow")) {
            return "/org/jpedal/objects/acroforms/res/Up-LeftArrow.png";
        } else if (name.equals("UpArrow")) {
            return "/org/jpedal/objects/acroforms/res/UpArrow.png";
        } else { //Default option. Name = Note
            return "/org/jpedal/objects/acroforms/res/TextNote.png";
        }
    }

    private static BufferedImage getFileAttachmentIcon() {

        final String iconFile = "/org/jpedal/objects/acroforms/res/FileAttachment.png";

        BufferedImage icon = null;
        try {
            icon = ImageIO.read(AnnotationFactory.class.getResource(iconFile));
        } catch (final IOException e) {
            LogWriter.writeLog("Exception: " + e.getMessage());
        }

        return icon;
    }

    private static BufferedImage getSoundIcon(final PdfObject form) {

        String iconFile = "/org/jpedal/objects/acroforms/res/Speaker.png";

        final String name = form.getName(PdfDictionary.Name);
        if (name != null && name.equals("Mic")) {
            iconFile = "/org/jpedal/objects/acroforms/res/Microphone.png";
        }

        BufferedImage icon = null;
        try {
            icon = ImageIO.read(AnnotationFactory.class.getResource(iconFile));
        } catch (final IOException e) {
            LogWriter.writeLog("Exception: " + e.getMessage());
        }

        return icon;
    }

    /**
     * Method to create an icon to represent the annotation and render it.
     *
     * @param form       :: PdfObject to hold the annotation data
     * @param current    :: DynamicVectorRender to draw the annotation
     * @param pageNumber :: Int value of the page number
     * @param rotation   :: Int value of the page rotation
     */
    public static void renderFlattenedAnnotation(final PdfObject form, final DynamicVectorRenderer current, final int pageNumber, final int rotation) {

        final BufferedImage image = AnnotationFactory.getIcon(form);

        if (image != null) {
            final GraphicsState gs = new GraphicsState();

            /*
             * now draw the finished image of the form
             */
            final int iconHeight = image.getHeight();
            final int iconWidth = image.getWidth();

            final float[] rect = form.getFloatArray(PdfDictionary.Rect);

            //Some Text annotations can have incorrect sizes so correct to icon size
            if (form.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Text) {
                rect[2] = rect[0] + iconWidth;
                rect[1] = rect[3] - iconHeight;
                form.setFloatArray(PdfDictionary.Rect, rect);

                //4 needed as we upsample by a factor of 4
                switch (rotation % 360) {
                    case 0:
                        gs.CTM = new float[][]{{iconWidth, 0, 1}, {0, iconHeight, 1}, {0, 0, 0}};

                        gs.x = rect[0];
                        gs.y = rect[3] - iconHeight;

                        // Draw onto image
                        gs.CTM[2][0] = rect[0];
                        gs.CTM[2][1] = rect[3] - iconHeight;
                        break;
                    case 90:
                        gs.CTM = new float[][]{{0, iconWidth, 1}, {-iconHeight, 0, 1}, {0, 0, 0}};

                        gs.x = rect[0] + iconHeight;
                        gs.y = rect[3];

                        // Draw onto image
                        gs.CTM[2][0] = rect[0] + iconHeight;
                        gs.CTM[2][1] = rect[3];
                        break;
                    case 180:
                        gs.CTM = new float[][]{{-iconWidth, 0, 1}, {0, -iconHeight, 1}, {0, 0, 0}};

                        gs.x = rect[0];
                        gs.y = rect[3] + iconHeight;

                        // Draw onto image
                        gs.CTM[2][0] = rect[0];
                        gs.CTM[2][1] = rect[3] + iconHeight;
                        break;
                    case 270:
                        gs.CTM = new float[][]{{0, -iconWidth, 1}, {iconHeight, 0, 1}, {0, 0, 0}};

                        gs.x = rect[0] - iconHeight;
                        gs.y = rect[3];

                        // Draw onto image
                        gs.CTM[2][0] = rect[0] - iconHeight;
                        gs.CTM[2][1] = rect[3];
                        break;
                }

            } else {
                // Code in switch statement was causing issues with 15jan/21353.pdf, rotating highlight annotation incorrectly
                // Icon contains no rotation so no need to factor out
                gs.CTM = new float[][]{{iconWidth, 0, 1}, {0, iconHeight, 1}, {0, 0, 0}};

                gs.x = rect[0];
                gs.y = rect[3] - iconHeight;

                // Draw onto image
                gs.CTM[2][0] = rect[0];
                gs.CTM[2][1] = rect[3] - iconHeight;
            }

            // Hard code blendMode for highlights to ensure correct output
            if (form.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Highlight) {
                current.setGraphicsState(GraphicsState.STROKE, gs.getAlpha(GraphicsState.STROKE), PdfDictionary.Darken);
                current.setGraphicsState(GraphicsState.FILL, gs.getAlpha(GraphicsState.FILL), PdfDictionary.Darken);
            }

            current.drawImage(pageNumber, image, gs, false, form.getObjectRefAsString(), -1);

            if (form.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Highlight) {
                current.setGraphicsState(GraphicsState.STROKE, gs.getAlpha(GraphicsState.STROKE), PdfDictionary.Normal);
                current.setGraphicsState(GraphicsState.FILL, gs.getAlpha(GraphicsState.FILL), PdfDictionary.Normal);
            }
        }
    }

    /**
     * Create a String to represent the current date.
     *
     * @return String representing the current date.
     * The String has the format "D:YYYYMMDDHHMMSS"
     * For instance January 1, 2000, 11:58:55 become D:20000101115855
     */
    public static String getCurrentDateAsString() {

        final Calendar cal = Calendar.getInstance();
        String date = "D:" + cal.get(Calendar.YEAR) + String.format("%02d", (cal.get(Calendar.MONTH)) + 1) + String.format("%02d", cal.get(Calendar.DAY_OF_MONTH)) + String.format("%02d", cal.get(Calendar.HOUR_OF_DAY)) + String.format("%02d", cal.get(Calendar.MINUTE)) + String.format("%02d", cal.get(Calendar.SECOND));
        final int offset = cal.getTimeZone().getOffset(System.currentTimeMillis());
        if (offset > 0) {
            date += '+';
        }
        if (offset != 0) {
            date += String.format("%02d", ((offset / 1000) / 3600));
            date += '\'';
            date += String.format("%02d", ((offset / 1000) % 3600) / 60);
            date += '\'';
        }
        return date;
    }

    /**
     * Method to create a curve from a series of points. This is used internally to generate the curve in ink annotations.
     *
     * @param points float array of values to use as point in the order x coord, y coords...
     *
     * @return float array of coords in alternating x / y order. The values being with a start coordinate at index 0 and 1.
     * The array then continues with 2 control points (next 4 values) and an end point for this curve which is also the
     * start point of the next curve (next 2 values). This repeats until the final end point is reached.
     */
    public static float[] createInkCurve(final float[] points) {

        float[] inkPoint = new float[points.length];
        inkPoint[0] = points[0];
        inkPoint[1] = points[1];

        int duplicateIDX = 2;

        for (int i = 2; i != points.length; i += 2) {
            if (inkPoint[duplicateIDX - 2] != points[i] ||
                    inkPoint[duplicateIDX - 1] != points[i + 1]) {
                inkPoint[duplicateIDX] = points[i];
                inkPoint[duplicateIDX + 1] = points[i + 1];
                duplicateIDX += 2;
            }
        }
        inkPoint = Arrays.copyOf(inkPoint, duplicateIDX);

        final float[] curvePoints = new float[(inkPoint.length * 3) - 4];

        curvePoints[0] = inkPoint[0];
        curvePoints[1] = inkPoint[1];
        float[] controlPoints = getControlPoints(inkPoint[0], inkPoint[1], inkPoint[0], inkPoint[1], inkPoint[2], inkPoint[3]);
        curvePoints[2] = controlPoints[2];
        curvePoints[3] = controlPoints[3];

        int idx = 4;

        for (int i = 2; i != inkPoint.length - 2; i += 2) {
            controlPoints = getControlPoints(inkPoint[i - 2], inkPoint[i - 1], inkPoint[i], inkPoint[i + 1], inkPoint[i + 2], inkPoint[i + 3]);
            curvePoints[idx] = controlPoints[0];
            idx++;

            curvePoints[idx] = controlPoints[1];
            idx++;

            curvePoints[idx] = inkPoint[i];
            idx++;

            curvePoints[idx] = inkPoint[i + 1];
            idx++;

            curvePoints[idx] = controlPoints[2];
            idx++;

            curvePoints[idx] = controlPoints[3];
            idx++;



        }

        controlPoints = getControlPoints(inkPoint[inkPoint.length - 4], inkPoint[inkPoint.length - 3], inkPoint[inkPoint.length - 2],
                inkPoint[inkPoint.length - 1], inkPoint[inkPoint.length - 2], inkPoint[inkPoint.length - 1]);
        curvePoints[curvePoints.length - 4] = controlPoints[0];
        curvePoints[curvePoints.length - 3] = controlPoints[1];
        curvePoints[curvePoints.length - 2] = inkPoint[inkPoint.length - 2];
        curvePoints[curvePoints.length - 1] = inkPoint[inkPoint.length - 1];

        return curvePoints;
    }

    /**
     * Generate to control points for a given point.
     * This method generates the control points for the curve connecting this point to the previous and next points.
     *
     * @param p1x x coord of the previous point
     * @param p1y y coord of the next point
     * @param p2x x coords of the point we are creating control points for
     * @param p2y y coords of the point we are creating control points for
     * @param p3x x coord of the previous point
     * @param p3y y coord of the next point
     *
     * @return float array with a length of 4. The first 2 values are the x and y coords of the control point for p2
     * whose curve ends at p1. The last 2 values are the x and y coords of the control point for p2 whose curve ends at p3.
     */
    private static float[] getControlPoints(final float p1x, final float p1y,
                                            final float p2x, final float p2y,
                                            final float p3x, final float p3y) {

        /*
         * The control points created follow the same angled line as the line from p1 to p3.
         *
         * The control point for p2 curving to p1 follows the line and direction from p3 to p1.
         * It is placed on this line at 1 third the distance of p2 to p1.
         *
         * The control point for p2 curving to p3 follows the line and direction from p1 to p3.
         * It is placed on this line at 1 third the distance of p2 to p3.
         *
         * To find these points we just perform some basic trigonometry detailed below.
         */

        final float[] controls = new float[4];

        //Get the sides of a right angled triangle where p3 to p1 is the longest side (sideC).
        final float sideA = p1x - p3x;
        final float sideB = p1y - p3y;
        final float sideC = (float) Math.sqrt((sideA * sideA) + (sideB * sideB));

        //Control 1
        //Get the control points distance from p2
        final float length1 = (float) Math.sqrt(((p1x - p2x) * (p1x - p2x)) + ((p1y - p2y) * (p1y - p2y))) / 3f;

        //Find scaling to scale sideC to the control point distance
        final float scale1 = length1 / sideC;

        //Scale down te other sides and add them to p2 to get the control point position, add value to follow the line
        controls[0] = (p2x + (sideA * scale1));
        controls[1] = (p2y + (sideB * scale1));

        //Control 2
        //Get the control points distance from p2
        final float length2 = (float) Math.sqrt(((p2x - p3x) * (p2x - p3x)) + ((p2y - p3y) * (p2y - p3y))) / 3f;

        //Find scaling to scale sideC to the control point distance
        final float scale2 = length2 / sideC;

        //Scale down te other sides and add them to p2 to get the control point position, instead of creating a new triangle
        //we can just minus the values from p2 to get the same results
        controls[2] = (p2x - (sideA * scale2));
        controls[3] = (p2y - (sideB * scale2));

        return controls;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy