# plugins/acroform.js

    /**
     * jsPDF AcroForm Plugin
     * Copyright (c) 2016 Alexander Weidt, https://github.com/BiggA94
     *
     * Licensed under the MIT License.
     * http://opensource.org/licenses/mit-license
     */

    (window.AcroForm = function (jsPDFAPI) {
        'use strict';

        var AcroForm = window.AcroForm;

        AcroForm.scale = function (x) {
            return (x * (acroformPlugin.internal.scaleFactor / 1));// 1 = (96 / 72)
        };
        AcroForm.antiScale = function (x) {
            return ((1 / acroformPlugin.internal.scaleFactor ) * x);
        };

        var acroformPlugin = {
            fields: [],
            xForms: [],
            /**
             * acroFormDictionaryRoot contains information about the AcroForm Dictionary
             * 0: The Event-Token, the AcroFormDictionaryCallback has
             * 1: The Object ID of the Root
             */
            acroFormDictionaryRoot: null,
            /**
             * After the PDF gets evaluated, the reference to the root has to be reset,
             * this indicates, whether the root has already been printed out
             */
            printedOut: false,
            internal: null
        };

        jsPDF.API.acroformPlugin = acroformPlugin;

        var annotReferenceCallback = function () {
            for (var i in this.acroformPlugin.acroFormDictionaryRoot.Fields) {
                var formObject = this.acroformPlugin.acroFormDictionaryRoot.Fields[i];
                // add Annot Reference!
                if (formObject.hasAnnotation) {
                    // If theres an Annotation Widget in the Form Object, put the Reference in the /Annot array
                    createAnnotationReference.call(this, formObject);
                }
            }
        };

        var createAcroForm = function () {
            if (this.acroformPlugin.acroFormDictionaryRoot) {
                //return;
                throw new Error("Exception while creating AcroformDictionary");
            }

            // The Object Number of the AcroForm Dictionary
            this.acroformPlugin.acroFormDictionaryRoot = new AcroForm.AcroFormDictionary();

            this.acroformPlugin.internal = this.internal;

            // add Callback for creating the AcroForm Dictionary
            this.acroformPlugin.acroFormDictionaryRoot._eventID = this.internal.events.subscribe('postPutResources', AcroFormDictionaryCallback);

            this.internal.events.subscribe('buildDocument', annotReferenceCallback); //buildDocument

            // Register event, that is triggered when the DocumentCatalog is written, in order to add /AcroForm
            this.internal.events.subscribe('putCatalog', putCatalogCallback);

            // Register event, that creates all Fields
            this.internal.events.subscribe('postPutPages', createFieldCallback);
        };

        /**
         * Create the Reference to the widgetAnnotation, so that it gets referenced in the Annot[] int the+
         * (Requires the Annotation Plugin)
         */
        var createAnnotationReference = function (object) {
            var options = {
                type: 'reference',
                object: object
            };
            jsPDF.API.annotationPlugin.annotations[this.internal.getPageInfo(object.page).pageNumber].push(options);
        };

        var putForm = function (formObject) {
            if (this.acroformPlugin.printedOut) {
                this.acroformPlugin.printedOut = false;
                this.acroformPlugin.acroFormDictionaryRoot = null;
            }
            if (!this.acroformPlugin.acroFormDictionaryRoot) {
                createAcroForm.call(this);
            }
            this.acroformPlugin.acroFormDictionaryRoot.Fields.push(formObject);
        };

        // Callbacks

        var putCatalogCallback = function () {
            //Put reference to AcroForm to DocumentCatalog
            if (typeof this.acroformPlugin.acroFormDictionaryRoot != 'undefined') { // for safety, shouldn't normally be the case
                this.internal.write('/AcroForm ' + this.acroformPlugin.acroFormDictionaryRoot.objId + ' '
                    + 0 + ' R');
            } else {
                console.log('Root missing...');
            }
        };

        /**
         * Adds /Acroform X 0 R to Document Catalog,
         * and creates the AcroForm Dictionary
         */
        var AcroFormDictionaryCallback = function () {
            // Remove event
            this.internal.events.unsubscribe(this.acroformPlugin.acroFormDictionaryRoot._eventID);

            delete this.acroformPlugin.acroFormDictionaryRoot._eventID;

            this.acroformPlugin.printedOut = true;
        };

        /**
         * Creates the single Fields and writes them into the Document
         *
         * If fieldArray is set, use the fields that are inside it instead of the fields from the AcroRoot
         * (for the FormXObjects...)
         */
        var createFieldCallback = function (fieldArray) {
            var standardFields = (!fieldArray);

            if (!fieldArray) {
                // in case there is no fieldArray specified, we want to print out the Fields of the AcroForm
                // Print out Root
                this.internal.newObjectDeferredBegin(this.acroformPlugin.acroFormDictionaryRoot.objId);
                this.internal.out(this.acroformPlugin.acroFormDictionaryRoot.getString());
            }

            var fieldArray = fieldArray || this.acroformPlugin.acroFormDictionaryRoot.Kids;

            for (var i in fieldArray) {
                var key = i;
                var form = fieldArray[i];

                var oldRect = form.Rect;

                if (form.Rect) {
                    form.Rect = AcroForm.internal.calculateCoordinates.call(this, form.Rect);
                }

                // Start Writing the Object
                this.internal.newObjectDeferredBegin(form.objId);

                var content = "";
                content += (form.objId + " 0 obj\n");

                content += ("<<\n" + form.getContent());

                form.Rect = oldRect;

                if (form.hasAppearanceStream && !form.appearanceStreamContent) {
                    // Calculate Appearance
                    var appearance = AcroForm.internal.calculateAppearanceStream.call(this, form);
                    content += "/AP << /N " + appearance + " >>\n";

                    this.acroformPlugin.xForms.push(appearance);
                }

                // Assume AppearanceStreamContent is a Array with N,R,D (at least one of them!)
                if (form.appearanceStreamContent) {
                    content += "/AP << ";
                    // Iterate over N,R and D
                    for (var k in form.appearanceStreamContent) {
                        var value = form.appearanceStreamContent[k];
                        content += ("/" + k + " ");
                        content += "<< ";
                        if (Object.keys(value).length >= 1 || Array.isArray(value)) {
                            // appearanceStream is an Array or Object!
                            for (var i in value) {
                                var obj = value[i];
                                if (typeof obj === 'function') {
                                    // if Function is referenced, call it in order to get the FormXObject
                                    obj = obj.call(this, form);
                                }
                                content += ("/" + i + " " + obj + " ");

                                // In case the XForm is already used, e.g. OffState of CheckBoxes, don't add it
                                if (!(this.acroformPlugin.xForms.indexOf(obj) >= 0))
                                    this.acroformPlugin.xForms.push(obj);
                            }
                        } else {
                            var obj = value;
                            if (typeof obj === 'function') {
                                // if Function is referenced, call it in order to get the FormXObject
                                obj = obj.call(this, form);
                            }
                            content += ("/" + i + " " + obj + " \n");
                            if (!(this.acroformPlugin.xForms.indexOf(obj) >= 0))
                                this.acroformPlugin.xForms.push(obj);
                        }
                        content += " >>\n";
                    }

                    // appearance stream is a normal Object..
                    content += (">>\n");
                }

                content += (">>\nendobj\n");

                this.internal.out(content);

            }
            if (standardFields) {
                createXFormObjectCallback.call(this, this.acroformPlugin.xForms);
            }
        };

        var createXFormObjectCallback = function (fieldArray) {
            for (var i in fieldArray) {
                var key = i;
                var form = fieldArray[i];
                // Start Writing the Object
                this.internal.newObjectDeferredBegin(form && form.objId);

                var content = "";
                content += form ? form.getString() : '';
                this.internal.out(content);

                delete fieldArray[key];
            }
        };

        // Public:

        jsPDFAPI.addField = function (fieldObject) {
            //var opt = parseOptions(fieldObject);
            if (fieldObject instanceof AcroForm.TextField) {
                addTextField.call(this, fieldObject);
            } else if (fieldObject instanceof AcroForm.ChoiceField) {
                addChoiceField.call(this, fieldObject);
            } else if (fieldObject instanceof AcroForm.Button) {
                addButton.call(this, fieldObject);
            } else if (fieldObject instanceof AcroForm.ChildClass) {
                putForm.call(this, fieldObject);
            } else if (fieldObject) {
                // try to put..
                putForm.call(this, fieldObject);
            }
            fieldObject.page = this.acroformPlugin.internal.getCurrentPageInfo().pageNumber;
            return this;
        };

        // ############### sort in:

        /**
         * Button
         * FT = Btn
         */
        var addButton = function (options) {
            var options = options || new AcroForm.Field();

            options.FT = '/Btn';

            /**
             * Calculating the Ff entry:
             *
             * The Ff entry contains flags, that have to be set bitwise
             * In the Following the number in the Comment is the BitPosition
             */
            var flags = options.Ff || 0;

            // 17, Pushbutton
            if (options.pushbutton) {
                // Options.pushbutton should be 1 or 0
                flags = AcroForm.internal.setBitPosition(flags, 17);
                delete options.pushbutton;
            }

            //16, Radio
            if (options.radio) {
                //flags = options.Ff | options.radio << 15;
                flags = AcroForm.internal.setBitPosition(flags, 16);
                delete options.radio;
            }

            // 15, NoToggleToOff (Radio buttons only
            if (options.noToggleToOff) {
                //flags = options.Ff | options.noToggleToOff << 14;
                flags = AcroForm.internal.setBitPosition(flags, 15);
                //delete options.noToggleToOff;
            }

            // In case, there is no Flag set, it is a check-box
            options.Ff = flags;

            putForm.call(this, options);

        };

        var addTextField = function (options) {
            var options = options || new AcroForm.Field();

            options.FT = '/Tx';

            /**
             * Calculating the Ff entry:
             *
             * The Ff entry contains flags, that have to be set bitwise
             * In the Following the number in the Comment is the BitPosition
             */

            var flags = options.Ff || 0;

            // 13, multiline
            if (options.multiline) {
                // Set Flag
                flags = flags | (1 << 12);
                // Remove multiline from FieldObject
                //delete options.multiline;
            }

            // 14, Password
            if (options.password) {
                flags = flags | (1 << 13);
                //delete options.password;
            }

            // 21, FileSelect, PDF 1.4...
            if (options.fileSelect) {
                flags = flags | (1 << 20);
                //delete options.fileSelect;
            }

            // 23, DoNotSpellCheck, PDF 1.4...
            if (options.doNotSpellCheck) {
                flags = flags | (1 << 22);
                //delete options.doNotSpellCheck;
            }

            // 24, DoNotScroll, PDF 1.4...
            if (options.doNotScroll) {
                flags = flags | (1 << 23);
                //delete options.doNotScroll;
            }

            options.Ff = options.Ff || flags;

            // Add field
            putForm.call(this, options);
        };

        var addChoiceField = function (opt) {
            var options = opt || new AcroForm.Field();

            options.FT = '/Ch';

            /**
             * Calculating the Ff entry:
             *
             * The Ff entry contains flags, that have to be set bitwise
             * In the Following the number in the Comment is the BitPosition
             */

            var flags = options.Ff || 0;

            // 18, Combo (If not set, the choiceField is a listBox!!)
            if (options.combo) {
                // Set Flag
                flags = AcroForm.internal.setBitPosition(flags, 18);
                // Remove combo from FieldObject
                delete options.combo;
            }

            // 19, Edit
            if (options.edit) {
                flags = AcroForm.internal.setBitPosition(flags, 19);
                delete options.edit;
            }

            // 20, Sort
            if (options.sort) {
                flags = AcroForm.internal.setBitPosition(flags, 20);
                delete options.sort;
            }

            // 22, MultiSelect (PDF 1.4)
            if (options.multiSelect && this.internal.getPDFVersion() >= 1.4) {
                flags = AcroForm.internal.setBitPosition(flags, 22);
                delete options.multiSelect;
            }

            // 23, DoNotSpellCheck (PDF 1.4)
            if (options.doNotSpellCheck && this.internal.getPDFVersion() >= 1.4) {
                flags = AcroForm.internal.setBitPosition(flags, 23);
                delete options.doNotSpellCheck;
            }

            options.Ff = flags;

            //options.hasAnnotation = true;

            // Add field
            putForm.call(this, options);
        };
    })(jsPDF.API);

    var AcroForm = window.AcroForm;

    AcroForm.internal = {};

    AcroForm.createFormXObject = function (formObject) {
        var xobj = new AcroForm.FormXObject;
        var height = AcroForm.Appearance.internal.getHeight(formObject) || 0;
        var width = AcroForm.Appearance.internal.getWidth(formObject) || 0;
        xobj.BBox = [0, 0, width, height];
        return xobj;
    };

    // Contains Methods for creating standard appearances
    AcroForm.Appearance = {
        CheckBox: {
            createAppearanceStream: function () {
                var appearance = {
                    N: {
                        On: AcroForm.Appearance.CheckBox.YesNormal
                    },
                    D: {
                        On: AcroForm.Appearance.CheckBox.YesPushDown,
                        Off: AcroForm.Appearance.CheckBox.OffPushDown
                    }
                };

                return appearance;
            },
            /**
             * If any other icons are needed, the number between the brackets can be changed
             * @returns {string}
             */
            createMK: function () {
                // 3-> Hook
                return "<< /CA (3)>>";
            },
            /**
             * Returns the standard On Appearance for a CheckBox
             * @returns {AcroForm.FormXObject}
             */
            YesPushDown: function (formObject) {
                var xobj = AcroForm.createFormXObject(formObject);
                var stream = "";
                // F13 is ZapfDingbats (Symbolic)
                formObject.Q = 1; // set text-alignment as centered
                var calcRes = AcroForm.internal.calculateX(formObject, "3", "ZapfDingbats", 50);
                stream += "0.749023 g\n\
                 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
                 f\n\
                 BMC\n\
                 q\n\
                 0 0 1 rg\n\
                 /F13 " + calcRes.fontSize + " Tf 0 g\n\
                 BT\n";
                stream += calcRes.text;
                stream += "ET\n\
                 Q\n\
                 EMC\n";
                xobj.stream = stream;
                return xobj;
            },

            YesNormal: function (formObject) {
                var xobj = AcroForm.createFormXObject(formObject);
                var stream = "";
                formObject.Q = 1; // set text-alignment as centered
                var calcRes = AcroForm.internal.calculateX(formObject, "3", "ZapfDingbats", AcroForm.Appearance.internal.getHeight(formObject) * 0.9);
                stream += "1 g\n\
    0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
    f\n\
    q\n\
    0 0 1 rg\n\
    0 0 " + (AcroForm.Appearance.internal.getWidth(formObject) - 1) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 1) + " re\n\
    W\n\
    n\n\
    0 g\n\
    BT\n\
    /F13 " + calcRes.fontSize + " Tf 0 g\n";
                stream += calcRes.text;
                stream += "ET\n\
                 Q\n";
                xobj.stream = stream;
                return xobj;
            },

            /**
             * Returns the standard Off Appearance for a CheckBox
             * @returns {AcroForm.FormXObject}
             */
            OffPushDown: function (formObject) {
                var xobj = AcroForm.createFormXObject(formObject);
                var stream = "";
                stream += "0.749023 g\n\
                0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
                f\n";
                xobj.stream = stream;
                return xobj;
            }
        },

        RadioButton: {
            Circle: {
                createAppearanceStream: function (name) {
                    var appearanceStreamContent = {
                        D: {
                            'Off': AcroForm.Appearance.RadioButton.Circle.OffPushDown
                        },
                        N: {}
                    };
                    appearanceStreamContent.N[name] = AcroForm.Appearance.RadioButton.Circle.YesNormal;
                    appearanceStreamContent.D[name] = AcroForm.Appearance.RadioButton.Circle.YesPushDown;
                    return appearanceStreamContent;
                },
                createMK: function () {
                    return "<< /CA (l)>>";
                },

                YesNormal: function (formObject) {
                    var xobj = AcroForm.createFormXObject(formObject);
                    var stream = "";
                    // Make the Radius of the Circle relative to min(height, width) of formObject
                    var DotRadius = (AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject)) ?
                    AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4;
                    // The Borderpadding...
                    DotRadius *= 0.9;
                    var c = AcroForm.Appearance.internal.Bezier_C;
                    /*
                     The Following is a Circle created with Bezier-Curves.
                     */
                    stream += "q\n\
    1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    " + DotRadius + " 0 m\n\
    " + DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c\n\
    -" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c\n\
    -" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c\n\
    " + DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c\n\
    f\n\
    Q\n";
                    xobj.stream = stream;
                    return xobj;
                },
                YesPushDown: function (formObject) {
                    var xobj = AcroForm.createFormXObject(formObject);
                    var stream = "";
                    var DotRadius = (AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject)) ?
                    AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4;
                    // The Borderpadding...
                    DotRadius *= 0.9;
                    // Save results for later use; no need to waste processor ticks on doing math
                    var k = DotRadius * 2;
                    // var c = AcroForm.Appearance.internal.Bezier_C;
                    var kc = k * AcroForm.Appearance.internal.Bezier_C;
                    var dc = DotRadius * AcroForm.Appearance.internal.Bezier_C;
    //                 stream += "0.749023 g\n\
    //             q\n\
    //           1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    // " + DotRadius * 2 + " 0 m\n\
    // " + DotRadius * 2 + " " + DotRadius * 2 * c + " " + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 " + DotRadius * 2 + " c\n\
    // -" + DotRadius * 2 * c + " " + DotRadius * 2 + " -" + DotRadius * 2 + " " + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 c\n\
    // -" + DotRadius * 2 + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 -" + DotRadius * 2 + " c\n\
    // " + DotRadius * 2 * c + " -" + DotRadius * 2 + " " + DotRadius * 2 + " -" + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 c\n\
    //             f\n\
    //             Q\n\
    //             0 g\n\
    //             q\n\
    //             1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    // " + DotRadius + " 0 m\n\
    // " + DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c\n\
    // -" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c\n\
    // -" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c\n\
    // " + DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c\n\
    //             f\n\
    //             Q\n";

    //  FASTER VERSION with less processor ticks spent on math operations
                    stream += "0.749023 g\n\
                q\n\
               1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    " + k + " 0 m\n\
    " + k + " " + kc + " " + kc + " " + k + " 0 " + k + " c\n\
    -" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c\n\
    -" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c\n\
    " + kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c\n\
                f\n\
                Q\n\
                0 g\n\
                q\n\
                1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    " + DotRadius + " 0 m\n\
    " + DotRadius + " " + dc + " " + dc + " " + DotRadius + " 0 " + DotRadius + " c\n\
    -" + dc + " " + DotRadius + " -" + DotRadius + " " + dc + " -" + DotRadius + " 0 c\n\
    -" + DotRadius + " -" + dc + " -" + dc + " -" + DotRadius + " 0 -" + DotRadius + " c\n\
    " + dc + " -" + DotRadius + " " + DotRadius + " -" + dc + " " + DotRadius + " 0 c\n\
                f\n\
                Q\n";
                    xobj.stream = stream;
                    return xobj;
                },
                OffPushDown: function (formObject) {
                    var xobj = AcroForm.createFormXObject(formObject);
                    var stream = "";
                    var DotRadius = (AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject)) ?
                    AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4;
                    // The Borderpadding...
                    DotRadius *= 0.9;
                    // Save results for later use; no need to waste processor ticks on doing math
                    var k = DotRadius * 2;
                    // var c = AcroForm.Appearance.internal.Bezier_C;
                    var kc = k * AcroForm.Appearance.internal.Bezier_C;
    //                 stream += "0.749023 g\n\
    //             q\n\
    //  1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    // " + DotRadius * 2 + " 0 m\n\
    // " + DotRadius * 2 + " " + DotRadius * 2 * c + " " + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 " + DotRadius * 2 + " c\n\
    // -" + DotRadius * 2 * c + " " + DotRadius * 2 + " -" + DotRadius * 2 + " " + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 c\n\
    // -" + DotRadius * 2 + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 -" + DotRadius * 2 + " c\n\
    // " + DotRadius * 2 * c + " -" + DotRadius * 2 + " " + DotRadius * 2 + " -" + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 c\n\
    //             f\n\
    //             Q\n";

    //  FASTER VERSION with less processor ticks spent on math operations
                    stream += "0.749023 g\n\
                q\n\
     1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
    " + k + " 0 m\n\
    " + k + " " + kc + " " + kc + " " + k + " 0 " + k + " c\n\
    -" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c\n\
    -" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c\n\
    " + kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c\n\
                f\n\
                Q\n";
                    xobj.stream = stream;
                    return xobj;
                },
            },

            Cross: {
                /**
                 * Creates the Actual AppearanceDictionary-References
                 * @param name
                 * @returns
                 */
                createAppearanceStream: function (name) {
                    var appearanceStreamContent = {
                        D: {
                            'Off': AcroForm.Appearance.RadioButton.Cross.OffPushDown
                        },
                        N: {}
                    };
                    appearanceStreamContent.N[name] = AcroForm.Appearance.RadioButton.Cross.YesNormal;
                    appearanceStreamContent.D[name] = AcroForm.Appearance.RadioButton.Cross.YesPushDown;
                    return appearanceStreamContent;
                },
                createMK: function () {
                    return "<< /CA (8)>>";
                },

                YesNormal: function (formObject) {
                    var xobj = AcroForm.createFormXObject(formObject);
                    var stream = "";
                    var cross = AcroForm.Appearance.internal.calculateCross(formObject);
                    stream += "q\n\
                1 1 " + (AcroForm.Appearance.internal.getWidth(formObject) - 2) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 2) + " re\n\
                W\n\
                n\n\
                " + cross.x1.x + " " + cross.x1.y + " m\n\
                " + cross.x2.x + " " + cross.x2.y + " l\n\
                " + cross.x4.x + " " + cross.x4.y + " m\n\
                " + cross.x3.x + " " + cross.x3.y + " l\n\
                s\n\
                Q\n";
                    xobj.stream = stream;
                    return xobj;
                },
                YesPushDown: function (formObject) {
                    var xobj = AcroForm.createFormXObject(formObject);
                    var cross = AcroForm.Appearance.internal.calculateCross(formObject);
                    var stream = "";
                    stream += "0.749023 g\n\
                0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
                f\n\
                q\n\
                1 1 " + (AcroForm.Appearance.internal.getWidth(formObject) - 2) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 2) + " re\n\
                W\n\
                n\n\
                " + cross.x1.x + " " + cross.x1.y + " m\n\
                " + cross.x2.x + " " + cross.x2.y + " l\n\
                " + cross.x4.x + " " + cross.x4.y + " m\n\
                " + cross.x3.x + " " + cross.x3.y + " l\n\
                s\n\
                Q\n";
                    xobj.stream = stream;
                    return xobj;
                },
                OffPushDown: function (formObject) {
                    var xobj = AcroForm.createFormXObject(formObject);
                    var stream = "";
                    stream += "0.749023 g\n\
                0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
                f\n";
                    xobj.stream = stream;
                    return xobj;
                }
            },
        },

        /**
         * Returns the standard Appearance
         * @returns {AcroForm.FormXObject}
         */
        createDefaultAppearanceStream: function (formObject) {
            var stream = "";
            // Set Helvetica to Standard Font (size: auto)
            // Color: Black
            stream += "/Helv 0 Tf 0 g";
            return stream;
        }
    };

    AcroForm.Appearance.internal = {
        Bezier_C: 0.551915024494,

        calculateCross: function (formObject) {
            var min = function (x, y) {
                return (x > y) ? y : x;
            };

            var width = AcroForm.Appearance.internal.getWidth(formObject);
            var height = AcroForm.Appearance.internal.getHeight(formObject);
            var a = min(width, height);
            var crossSize = a;
            var borderPadding = 2; // The Padding in px

            var cross = {
                x1: { // upperLeft
                    x: (width - a) / 2,
                    y: ((height - a) / 2) + a,//height - borderPadding
                },
                x2: { // lowerRight
                    x: ((width - a) / 2) + a,
                    y: ((height - a) / 2)//borderPadding
                },
                x3: { // lowerLeft
                    x: (width - a) / 2,
                    y: ((height - a) / 2)//borderPadding
                },
                x4: { // upperRight
                    x: ((width - a) / 2) + a,
                    y: ((height - a) / 2) + a,//height - borderPadding
                }
            };

            return cross;
        },
    };
    AcroForm.Appearance.internal.getWidth = function (formObject) {
        return formObject.Rect[2];//(formObject.Rect[2] - formObject.Rect[0]) || 0;
    };
    AcroForm.Appearance.internal.getHeight = function (formObject) {
        return formObject.Rect[3];//(formObject.Rect[1] - formObject.Rect[3]) || 0;
    };

    // ##########################

    //### For inheritance:
    AcroForm.internal.inherit = function (child, parent) {
        var ObjectCreate = Object.create || function (o) {
                var F = function () {
                };
                F.prototype = o;
                return new F();
            };
        child.prototype = Object.create(parent.prototype);
        child.prototype.constructor = child;
    };

    // ### Handy Functions:

    AcroForm.internal.arrayToPdfArray = function (array) {
        if (Array.isArray(array)) {
            var content = ' [';
            for (var i in array) {
                var element = array[i].toString();
                content += element;
                content += ((i < array.length - 1) ? ' ' : '');
            }
            content += ']';

            return content;
        }
    };

    AcroForm.internal.toPdfString = function (string) {
        string = string || "";

        // put Bracket at the Beginning of the String
        if (string.indexOf('(') !== 0) {
            string = '(' + string;
        }

        if (string.substring(string.length - 1) != ')') {
            string += '(';
        }
        return string;
    };

    // ##########################
    //          Classes
    // ##########################

    AcroForm.PDFObject = function () {
        // The Object ID in the PDF Object Model
        // todo
        var _objId;
        Object.defineProperty(this, 'objId', {
            get: function () {
                if (!_objId) {
                    if (this.internal) {
                        _objId = this.internal.newObjectDeferred();
                    } else if (jsPDF.API.acroformPlugin.internal) {
                        // todo - find better option, that doesn't rely on a Global Static var
                        _objId = jsPDF.API.acroformPlugin.internal.newObjectDeferred();
                    }
                }
                if (!_objId) {
                    console.log("Couldn't create Object ID");
                }
                return _objId
            },
            configurable: false
        });
    };

    AcroForm.PDFObject.prototype.toString = function () {
        return this.objId + " 0 R";
    };

    AcroForm.PDFObject.prototype.getString = function () {
        var res = this.objId + " 0 obj\n<<";
        var content = this.getContent();

        res += content + ">>\n";
        if (this.stream) {
            res += "stream\n";
            res += this.stream;
            res += "endstream\n";
        }
        res += "endobj\n";
        return res;
    };

    AcroForm.PDFObject.prototype.getContent = function () {
        /**
         * Prints out all enumerable Variables from the Object
         * @param fieldObject
         * @returns {string}
         */
        var createContentFromFieldObject = function (fieldObject) {
            var content = '';

            var keys = Object.keys(fieldObject).filter(function (key) {
                return (key != 'content' && key != 'appearanceStreamContent' && key.substring(0, 1) != "_");
            });

            for (var i in keys) {
                var key = keys[i];
                var value = fieldObject[key];

                /*if (key == 'Rect' && value) {
                 value = AcroForm.internal.calculateCoordinates.call(jsPDF.API.acroformPlugin.internal, value);
                 }*/

                if (value) {
                    if (Array.isArray(value)) {
                        content += '/' + key + ' ' + AcroForm.internal.arrayToPdfArray(value) + "\n";
                    } else if (value instanceof AcroForm.PDFObject) {
                        // In case it is a reference to another PDFObject, take the referennce number
                        content += '/' + key + ' ' + value.objId + " 0 R" + "\n";
                    } else {
                        content += '/' + key + ' ' + value + '\n';
                    }
                }
            }
            return content;
        };

        var object = "";

        object += createContentFromFieldObject(this);
        return object;
    };

    AcroForm.FormXObject = function () {
        AcroForm.PDFObject.call(this);
        this.Type = "/XObject";
        this.Subtype = "/Form";
        this.FormType = 1;
        this.BBox;
        this.Matrix;
        this.Resources = "2 0 R";
        this.PieceInfo;
        var _stream;
        Object.defineProperty(this, 'Length', {
            enumerable: true,
            get: function () {
                return (_stream !== undefined) ? _stream.length : 0;
            }
        });
        Object.defineProperty(this, 'stream', {
            enumerable: false,
            set: function (val) {
                _stream = val;
            },
            get: function () {
                if (_stream) {
                    return _stream;
                } else {
                    return null;
                }
            }
        });
    };

    AcroForm.internal.inherit(AcroForm.FormXObject, AcroForm.PDFObject);

    AcroForm.AcroFormDictionary = function () {
        AcroForm.PDFObject.call(this);
        var _Kids = [];
        Object.defineProperty(this, 'Kids', {
            enumerable: false,
            configurable: true,
            get: function () {
                if (_Kids.length > 0) {
                    return _Kids;
                } else {
                    return;
                }
            }
        });
        Object.defineProperty(this, 'Fields', {
            enumerable: true,
            configurable: true,
            get: function () {
                return _Kids;
            }
        });
        // Default Appearance
        this.DA;
    };

    AcroForm.internal.inherit(AcroForm.AcroFormDictionary, AcroForm.PDFObject);

    // ##### The Objects, the User can Create:

    // The Field Object contains the Variables, that every Field needs
    // Rectangle for Appearance: lower_left_X, lower_left_Y, width, height
    AcroForm.Field = function () {
        'use strict';
        AcroForm.PDFObject.call(this);

        var _Rect;
        Object.defineProperty(this, 'Rect', {
            enumerable: true,
            configurable: false,
            get: function () {
                if (!_Rect) {
                    return;
                }
                var tmp = _Rect;
                //var calculatedRes = AcroForm.internal.calculateCoordinates(_Rect); // do later!
                return tmp
            },
            set: function (val) {
                _Rect = val;
            }
        });

        var _FT = "";
        Object.defineProperty(this, 'FT', {
            enumerable: true,
            set: function (val) {
                _FT = val
            },
            get: function () {
                return _FT
            }
        });
        /**
         * The Partial name of the Field Object.
         * It has to be unique.
         */
        var _T;

        Object.defineProperty(this, 'T', {
            enumerable: true,
            configurable: false,
            set: function (val) {
                _T = val;
            },
            get: function () {
                if (!_T || _T.length < 1) {
                    if (this instanceof AcroForm.ChildClass) {
                        // In case of a Child from a Radio´Group, you don't need a FieldName!!!
                        return;
                    }
                    return "(FieldObject" + (AcroForm.Field.FieldNum++) + ")";
                }
                if (_T.substring(0, 1) == "(" && _T.substring(_T.length - 1)) {
                    return _T;
                }
                return "(" + _T + ")";
            }
        });

        var _DA;
        // Defines the default appearance (Needed for variable Text)
        Object.defineProperty(this, 'DA', {
            enumerable: true,
            get: function () {
                if (!_DA) {
                    return;
                }
                return '(' + _DA + ')'
            },
            set: function (val) {
                _DA = val
            }
        });

        var _DV;
        // Defines the default value
        Object.defineProperty(this, 'DV', {
            enumerable: true,
            configurable: true,
            get: function () {
                if (!_DV) {
                    return;
                }
                return _DV
            },
            set: function (val) {
                _DV = val
            }
        });

        //this.Type = "/Annot";
        //this.Subtype = "/Widget";
        Object.defineProperty(this, 'Type', {
            enumerable: true,
            get: function () {
                return (this.hasAnnotation) ? "/Annot" : null;
            }
        });

        Object.defineProperty(this, 'Subtype', {
            enumerable: true,
            get: function () {
                return (this.hasAnnotation) ? "/Widget" : null;
            }
        });

        /**
         *
         * @type {Array}
         */
        this.BG;

        Object.defineProperty(this, 'hasAnnotation', {
            enumerable: false,
            get: function () {
                if (this.Rect || this.BC || this.BG) {
                    return true
                }
                return false;
            }
        });

        Object.defineProperty(this, 'hasAppearanceStream', {
            enumerable: false,
            configurable: true,
            writable: true
        });

        Object.defineProperty(this, 'page', {
            enumerable: false,
            configurable: true,
            writable: true
        });
    };
    AcroForm.Field.FieldNum = 0;

    AcroForm.internal.inherit(AcroForm.Field, AcroForm.PDFObject);

    AcroForm.ChoiceField = function () {
        AcroForm.Field.call(this);
        // Field Type = Choice Field
        this.FT = "/Ch";
        // options
        this.Opt = [];
        this.V = '()';
        // Top Index
        this.TI = 0;
        /**
         * Defines, whether the
         * @type {boolean}
         */
        this.combo = false;
        /**
         * Defines, whether the Choice Field is an Edit Field.
         * An Edit Field is automatically an Combo Field.
         */
        Object.defineProperty(this, 'edit', {
            enumerable: true,
            set: function (val) {
                if (val == true) {
                    this._edit = true;
                    // ComboBox has to be true
                    this.combo = true;
                } else {
                    this._edit = false;
                }
            },
            get: function () {
                if (!this._edit) {
                    return false;
                }
                return this._edit;
            },
            configurable: false
        });
        this.hasAppearanceStream = true;
        Object.defineProperty(this, 'V', {
            get: function() {
                AcroForm.internal.toPdfString();
            }
        });
    };
    AcroForm.internal.inherit(AcroForm.ChoiceField, AcroForm.Field);
    window["ChoiceField"] = AcroForm.ChoiceField;

    AcroForm.ListBox = function () {
        AcroForm.ChoiceField.call(this);
        //var combo = true;
    };
    AcroForm.internal.inherit(AcroForm.ListBox, AcroForm.ChoiceField);
    window["ListBox"] = AcroForm.ListBox;

    AcroForm.ComboBox = function () {
        AcroForm.ListBox.call(this);
        this.combo = true;
    };
    AcroForm.internal.inherit(AcroForm.ComboBox, AcroForm.ListBox);
    window["ComboBox"] = AcroForm.ComboBox;

    AcroForm.EditBox = function () {
        AcroForm.ComboBox.call(this);
        this.edit = true;
    };
    AcroForm.internal.inherit(AcroForm.EditBox, AcroForm.ComboBox);
    window["EditBox"] = AcroForm.EditBox;

    AcroForm.Button = function () {
        AcroForm.Field.call(this);
        this.FT = "/Btn";
        //this.hasAnnotation = true;
    };
    AcroForm.internal.inherit(AcroForm.Button, AcroForm.Field);
    window["Button"] = AcroForm.Button;

    AcroForm.PushButton = function () {
        AcroForm.Button.call(this);
        this.pushbutton = true;
    };
    AcroForm.internal.inherit(AcroForm.PushButton, AcroForm.Button);
    window["PushButton"] = AcroForm.PushButton;

    AcroForm.RadioButton = function () {
        AcroForm.Button.call(this);
        this.radio = true;
        var _Kids = [];
        Object.defineProperty(this, 'Kids', {
            enumerable: true,
            get: function () {
                if (_Kids.length > 0) {
                    return _Kids;
                }
            }
        });

        Object.defineProperty(this, '__Kids', {
            get: function () {
                return _Kids;
            }
        });

        var _noToggleToOff;

        Object.defineProperty(this, 'noToggleToOff', {
            enumerable: false,
            get: function () {
                return _noToggleToOff
            },
            set: function (val) {
                _noToggleToOff = val
            }
        });

        //this.hasAnnotation = false;
    };
    AcroForm.internal.inherit(AcroForm.RadioButton, AcroForm.Button);
    window["RadioButton"] = AcroForm.RadioButton;

    /*
     * The Child classs of a RadioButton (the radioGroup)
     * -> The single Buttons
     */
    AcroForm.ChildClass = function (parent, name) {
        AcroForm.Field.call(this);
        this.Parent = parent;

        // todo: set AppearanceType as variable that can be set from the outside...
        this._AppearanceType = AcroForm.Appearance.RadioButton.Circle; // The Default appearanceType is the Circle
        this.appearanceStreamContent = this._AppearanceType.createAppearanceStream(name);

        // Set Print in the Annot Flag
        this.F = AcroForm.internal.setBitPosition(this.F, 3, 1);

        // Set AppearanceCharacteristicsDictionary with default appearance if field is not interacting with user
        this.MK = this._AppearanceType.createMK(); // (8) -> Cross, (1)-> Circle, ()-> nothing

        // Default Appearance is Off
        this.AS = "/Off";// + name;

        this._Name = name;
    };
    AcroForm.internal.inherit(AcroForm.ChildClass, AcroForm.Field);

    AcroForm.RadioButton.prototype.setAppearance = function (appearance) {
        if (!('createAppearanceStream' in appearance && 'createMK' in appearance)) {
            console.log("Couldn't assign Appearance to RadioButton. Appearance was Invalid!");
            return;
        }
        for (var i in this.__Kids) {
            var child = this.__Kids[i];

            child.appearanceStreamContent = appearance.createAppearanceStream(child._Name);
            child.MK = appearance.createMK();
        }
    };

    AcroForm.RadioButton.prototype.createOption = function (name) {
        var parent = this;
        var kidCount = this.__Kids.length;

        // Create new Child for RadioGroup
        var child = new AcroForm.ChildClass(parent, name);
        // Add to Parent
        this.__Kids.push(child);

        jsPDF.API.addField(child);

        return child;
    };

    AcroForm.CheckBox = function () {
        Button.call(this);
        this.appearanceStreamContent = AcroForm.Appearance.CheckBox.createAppearanceStream();
        this.MK = AcroForm.Appearance.CheckBox.createMK();
        this.AS = "/On";
        this.V = "/On";
    };
    AcroForm.internal.inherit(AcroForm.CheckBox, AcroForm.Button);
    window["CheckBox"] = AcroForm.CheckBox;

    AcroForm.TextField = function () {
        AcroForm.Field.call(this);
        this.DA = AcroForm.Appearance.createDefaultAppearanceStream();
        this.F = 4;
        var _V;
        Object.defineProperty(this, 'V', {
            get: function () {
                if (_V) {
                    return "(" + _V + ")"
                }
                else {
                    return _V
                }
            },
            enumerable: true,
            set: function (val) {
                _V = val
            }
        });

        var _DV;
        Object.defineProperty(this, 'DV', {
            get: function () {
                if (_DV) {
                    return "(" + _DV + ")"
                }
                else {
                    return _DV
                }
            },
            enumerable: true,
            set: function (val) {
                _DV = val
            }
        });

        var _multiline = false;
        Object.defineProperty(this, 'multiline', {
            enumerable: false,
            get: function () {
                return _multiline
            },
            set: function (val) {
                _multiline = val;
            }
        });

        //this.multiline = false;
        //this.password = false;
        /**
         * For PDF 1.4
         * @type {boolean}
         */
        //this.fileSelect = false;
        /**
         * For PDF 1.4
         * @type {boolean}
         */
        //this.doNotSpellCheck = false;
        /**
         * For PDF 1.4
         * @type {boolean}
         */
            //this.doNotScroll = false;

        var _MaxLen = false;
        Object.defineProperty(this, 'MaxLen', {
            enumerable: true,
            get: function () {
                return _MaxLen;
            },
            set: function (val) {
                _MaxLen = val;
            }
        });

        Object.defineProperty(this, 'hasAppearanceStream', {
            enumerable: false,
            get: function () {
                return (this.V || this.DV);
            }
        });

    };
    AcroForm.internal.inherit(AcroForm.TextField, AcroForm.Field);
    window["TextField"] = AcroForm.TextField;

    AcroForm.PasswordField = function () {
        TextField.call(this);
        Object.defineProperty(this, 'password', {
            value: true,
            enumerable: false,
            configurable: false,
            writable: false
        });
    };
    AcroForm.internal.inherit(AcroForm.PasswordField, AcroForm.TextField);
    window["PasswordField"] = AcroForm.PasswordField;

    // ############ internal functions

    /*
     * small workaround for calculating the TextMetric approximately
     * @param text
     * @param fontsize
     * @returns {TextMetrics} (Has Height and Width)
     */
    AcroForm.internal.calculateFontSpace = function (text, fontsize, fonttype) {
        var fonttype = fonttype || "helvetica";
        //re-use canvas object for speed improvements
        var canvas = AcroForm.internal.calculateFontSpace.canvas || (AcroForm.internal.calculateFontSpace.canvas = document.createElement('canvas'));

        var context = canvas.getContext('2d');
        context.save();
        var newFont = (fontsize + " " + fonttype);
        context.font = newFont;
        var res = context.measureText(text);
        context.fontcolor = 'black';
        // Calculate height:
        var context = canvas.getContext('2d');
        res.height = context.measureText("3").width * 1.5; // 3 because in ZapfDingbats its a Hook and a 3 in normal fonts
        context.restore();

        var width = res.width;

        return res;
    };

    AcroForm.internal.calculateX = function (formObject, text, font, maxFontSize) {
        var maxFontSize = maxFontSize || 12;
        var font = font || "helvetica";
        var returnValue = {
            text: "",
            fontSize: ""
        };
        // Remove Brackets
        text = (text.substr(0, 1) == '(') ? text.substr(1) : text;
        text = (text.substr(text.length - 1) == ')') ? text.substr(0, text.length - 1) : text;
        // split into array of words
        var textSplit = text.split(' ');

        /**
         * the color could be ((alpha)||(r,g,b)||(c,m,y,k))
         * @type {string}
         */
        var color = "0 g\n";
        var fontSize = maxFontSize; // The Starting fontSize (The Maximum)
        var lineSpacing = 2;
        var borderPadding = 2;

        var height = AcroForm.Appearance.internal.getHeight(formObject) || 0;
        height = (height < 0) ? -height : height;
        var width = AcroForm.Appearance.internal.getWidth(formObject) || 0;
        width = (width < 0) ? -width : width;

        var isSmallerThanWidth = function (i, lastLine, fontSize) {
            if (i + 1 < textSplit.length) {
                var tmp = lastLine + " " + textSplit[i + 1];
                var TextWidth = ((AcroForm.internal.calculateFontSpace(tmp, fontSize + "px", font).width));
                var FieldWidth = (width - 2 * borderPadding);
                return (TextWidth <= FieldWidth);
            } else {
                return false;
            }
        };

        fontSize++;
        FontSize: while (true) {
            var text = "";
            fontSize--;
            var textHeight = AcroForm.internal.calculateFontSpace("3", fontSize + "px", font).height;
            var startY = (formObject.multiline) ? height - fontSize : (height - textHeight) / 2;
            startY += lineSpacing;
            var startX = -borderPadding;

            var lastX = startX, lastY = startY;
            var firstWordInLine = 0, lastWordInLine = 0;
            var lastLength = 0;

            var y = 0;
            if (fontSize == 0) {
                // In case, the Text doesn't fit at all
                fontSize = 12;
                text = "(...) Tj\n";
                text += "% Width of Text: " + AcroForm.internal.calculateFontSpace(text, "1px").width + ", FieldWidth:" + width + "\n";
                break;
            }

            lastLength = AcroForm.internal.calculateFontSpace(textSplit[0] + " ", fontSize + "px", font).width;

            var lastLine = "";
            var lineCount = 0;
            Line:
                for (var i in textSplit) {
                    lastLine += textSplit[i] + " ";
                    // Remove last blank
                    lastLine = (lastLine.substr(lastLine.length - 1) == " ") ? lastLine.substr(0, lastLine.length - 1) : lastLine;
                    var key = parseInt(i);
                    lastLength = AcroForm.internal.calculateFontSpace(lastLine + " ", fontSize + "px", font).width;
                    var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize);
                    var isLastWord = i >= textSplit.length - 1;
                    if (nextLineIsSmaller && !isLastWord) {
                        lastLine += " ";
                        continue; // Line
                    } else if (!nextLineIsSmaller && !isLastWord) {
                        if (!formObject.multiline) {
                            continue FontSize;
                        } else {
                            if (((textHeight + lineSpacing) * (lineCount + 2) + lineSpacing) > height) {
                                // If the Text is higher than the FieldObject
                                continue FontSize;
                            }
                            lastWordInLine = key;
                            // go on
                        }
                    } else if (isLastWord) {
                        lastWordInLine = key;
                    } else {
                        if (formObject.multiline && ((textHeight + lineSpacing) * (lineCount + 2) + lineSpacing) > height) {
                            // If the Text is higher than the FieldObject
                            continue FontSize;
                        }
                    }

                    var line = '';

                    for (var x = firstWordInLine; x <= lastWordInLine; x++) {
                        line += textSplit[x] + ' ';
                    }

                    // Remove last blank
                    line = (line.substr(line.length - 1) == " ") ? line.substr(0, line.length - 1) : line;
                    //lastLength -= blankSpace.width;
                    lastLength = AcroForm.internal.calculateFontSpace(line, fontSize + "px", font).width;

                    // Calculate startX
                    switch (formObject.Q) {
                        case 2: // Right justified
                            startX = (width - lastLength - borderPadding);
                            break;
                        case 1:// Q = 1 := Text-Alignment: Center
                            startX = (width - lastLength) / 2;
                            break;
                        case 0:
                        default:
                            startX = borderPadding;
                            break;
                    }
                    text += (startX) + ' ' + (lastY) + ' Td\n';
                    text += '(' + line + ') Tj\n';
                    // reset X in PDF
                    text += (-startX) + ' 0 Td\n';

                    // After a Line, adjust y position
                    lastY = -(fontSize + lineSpacing);
                    lastX = startX;

                    // Reset for next iteration step
                    lastLength = 0;
                    firstWordInLine = lastWordInLine + 1;
                    lineCount++;

                    lastLine = "";
                    continue Line;
                }
            break;
        }

        returnValue.text = text;
        returnValue.fontSize = fontSize;

        return returnValue;
    };

    AcroForm.internal.calculateAppearanceStream = function (formObject) {
        if (formObject.appearanceStreamContent) {
            // If appearanceStream is already set, use it
            return formObject.appearanceStreamContent;
        }

        if (!formObject.V && !formObject.DV) {
            return;
        }

        // else calculate it

        var stream = '';

        var text = formObject.V || formObject.DV;

        var calcRes = AcroForm.internal.calculateX(formObject, text);

        stream += '/Tx BMC\n' +
            'q\n' +
                //color + '\n' +
            '/F1 ' + calcRes.fontSize + ' Tf\n' +
                // Text Matrix
            '1 0 0 1 0 0 Tm\n';
        // Begin Text
        stream += 'BT\n';
        stream += calcRes.text;
        // End Text
        stream += 'ET\n';
        stream += 'Q\n' +
            'EMC\n';

        var appearanceStreamContent = new AcroForm.createFormXObject(formObject);

        appearanceStreamContent.stream = stream;

        var appearance = {
            N: {
                'Normal': appearanceStreamContent
            }
        };

        return appearanceStreamContent;
    };

    /*
     * Converts the Parameters from x,y,w,h to lowerLeftX, lowerLeftY, upperRightX, upperRightY
     * @param x
     * @param y
     * @param w
     * @param h
     * @returns {*[]}
     */
    AcroForm.internal.calculateCoordinates = function (x, y, w, h) {
        var coordinates = {};

        if (this.internal) {
            function mmtopx(x) {
                return (x * this.internal.scaleFactor);
            }

            if (Array.isArray(x)) {
                x[0] = AcroForm.scale(x[0]);
                x[1] = AcroForm.scale(x[1]);
                x[2] = AcroForm.scale(x[2]);
                x[3] = AcroForm.scale(x[3]);

                coordinates.lowerLeft_X = x[0] || 0;
                coordinates.lowerLeft_Y = (mmtopx.call(this, this.internal.pageSize.height) - x[3] - x[1]) || 0;
                coordinates.upperRight_X = x[0] + x[2] || 0;
                coordinates.upperRight_Y = (mmtopx.call(this, this.internal.pageSize.height) - x[1]) || 0;
            } else {
                x = AcroForm.scale(x);
                y = AcroForm.scale(y);
                w = AcroForm.scale(w);
                h = AcroForm.scale(h);
                coordinates.lowerLeft_X = x || 0;
                coordinates.lowerLeft_Y = this.internal.pageSize.height - y || 0;
                coordinates.upperRight_X = x + w || 0;
                coordinates.upperRight_Y = this.internal.pageSize.height - y + h || 0;
            }
        } else {
            // old method, that is fallback, if we can't get the pageheight, the coordinate-system starts from lower left
            if (Array.isArray(x)) {
                coordinates.lowerLeft_X = x[0] || 0;
                coordinates.lowerLeft_Y = x[1] || 0;
                coordinates.upperRight_X = x[0] + x[2] || 0;
                coordinates.upperRight_Y = x[1] + x[3] || 0;
            } else {
                coordinates.lowerLeft_X = x || 0;
                coordinates.lowerLeft_Y = y || 0;
                coordinates.upperRight_X = x + w || 0;
                coordinates.upperRight_Y = y + h || 0;
            }
        }

        return [coordinates.lowerLeft_X, coordinates.lowerLeft_Y, coordinates.upperRight_X, coordinates.upperRight_Y];
    };

    AcroForm.internal.calculateColor = function (r, g, b) {
        var color = new Array(3);
        color.r = r | 0;
        color.g = g | 0;
        color.b = b | 0;
        return color;
    };

    AcroForm.internal.getBitPosition = function (variable, position) {
        variable = variable || 0;
        var bitMask = 1;
        bitMask = bitMask << (position - 1);
        return variable | bitMask;
    };

    AcroForm.internal.setBitPosition = function (variable, position, value) {
        variable = variable || 0;
        value = value || 1;

        var bitMask = 1;
        bitMask = bitMask << (position - 1);

        if (value == 1) {
            // Set the Bit to 1
            var variable = variable | bitMask;
        } else {
            // Set the Bit to 0
            var variable = variable & (~bitMask);
        }

        return variable;
    };