/**
* jsPDF AcroForm Plugin Copyright (c) 2016 Alexander Weidt,
* https://github.com/BiggA94
*
* Licensed under the MIT License. http://opensource.org/licenses/mit-license
*/
(function(jsPDFAPI, globalObj) {
"use strict";
var scope;
var pageHeight;
var scaleFactor = 1;
var 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;
};
var scale = function(x) {
return x * (scaleFactor / 1); // 1 = (96 / 72)
};
var antiScale = function(x) {
return (1 / scaleFactor) * x;
};
var createFormXObject = function(formObject) {
var xobj = new AcroFormXObject();
var height = AcroFormAppearance.internal.getHeight(formObject) || 0;
var width = AcroFormAppearance.internal.getWidth(formObject) || 0;
xobj.BBox = [0, 0, width.toFixed(2), height.toFixed(2)];
return xobj;
};
var 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;
};
var getBitPosition = function(variable, position) {
variable = variable || 0;
var bitMask = 1;
bitMask = bitMask << (position - 1);
return variable | bitMask;
};
/**
* 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 calculateFlagsOnOptions = function(flags, opts, PDFVersion) {
var PDFVersion = PDFVersion || 1.3;
var flags = flags || 0;
// 1, readOnly
if (opts.readOnly == true) {
flags = setBitPosition(flags, 1);
}
// 2, required
if (opts.required == true) {
flags = setBitPosition(flags, 2);
}
// 4, noExport
if (opts.noExport == true) {
flags = setBitPosition(flags, 3);
}
// 13, multiline
if (opts.multiline == true) {
flags = setBitPosition(flags, 13);
}
// 14, Password
if (opts.password) {
flags = setBitPosition(flags, 14);
}
// 15, NoToggleToOff (Radio buttons only
if (opts.noToggleToOff) {
flags = setBitPosition(flags, 15);
}
// 16, Radio
if (opts.radio) {
flags = setBitPosition(flags, 16);
}
// 17, Pushbutton
if (opts.pushbutton) {
flags = setBitPosition(flags, 17);
}
// 18, Combo (If not set, the choiceField is a listBox!!)
if (opts.combo) {
flags = setBitPosition(flags, 18);
}
// 19, Edit
if (opts.edit) {
flags = setBitPosition(flags, 19);
}
// 20, Sort
if (opts.sort) {
flags = setBitPosition(flags, 20);
}
// 21, FileSelect, PDF 1.4...
if (opts.fileSelect && PDFVersion >= 1.4) {
flags = setBitPosition(flags, 21);
}
// 22, MultiSelect (PDF 1.4)
if (opts.multiSelect && PDFVersion >= 1.4) {
flags = setBitPosition(flags, 22);
}
// 23, DoNotSpellCheck (PDF 1.4)
if (opts.doNotSpellCheck && PDFVersion >= 1.4) {
flags = setBitPosition(flags, 23);
}
// 24, DoNotScroll (PDF 1.4)
if (opts.doNotScroll == true && PDFVersion >= 1.4) {
flags = setBitPosition(flags, 24);
}
// 25, RichText (PDF 1.4)
if (opts.richText && PDFVersion >= 1.4) {
flags = setBitPosition(flags, 25);
}
return flags;
};
var calculateCoordinates = function(args) {
var x = args[0];
var y = args[1];
var w = args[2];
var h = args[3];
var coordinates = {};
if (Array.isArray(x)) {
x[0] = scale(x[0]);
x[1] = scale(x[1]);
x[2] = scale(x[2]);
x[3] = scale(x[3]);
} else {
x = scale(x);
y = scale(y);
w = scale(w);
h = scale(h);
}
coordinates.lowerLeft_X = x || 0;
coordinates.lowerLeft_Y = scale(pageHeight) - y - h || 0;
coordinates.upperRight_X = x + w || 0;
coordinates.upperRight_Y = scale(pageHeight) - y || 0;
return [
coordinates.lowerLeft_X.toFixed(2),
coordinates.lowerLeft_Y.toFixed(2),
coordinates.upperRight_X.toFixed(2),
coordinates.upperRight_Y.toFixed(2)
];
};
var 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 = calculateX(formObject, text);
stream.push("/Tx BMC");
stream.push("q");
stream.push("/F1 " + calcRes.fontSize.toFixed(2) + " Tf");
stream.push("1 0 0 1 0 0 Tm"); // Text Matrix
stream.push("BT"); // Begin Text
stream.push(calcRes.text);
stream.push("ET"); // End Text
stream.push("Q");
stream.push("EMC");
var appearanceStreamContent = new createFormXObject(formObject);
appearanceStreamContent.stream = stream.join("\n");
var appearance = {
N: {
Normal: appearanceStreamContent
}
};
return appearanceStreamContent;
};
var 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 = AcroFormAppearance.internal.getHeight(formObject) || 0;
height = height < 0 ? -height : height;
var width = AcroFormAppearance.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 = 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 = 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: " + calculateFontSpace(text, "1px").width + ", FieldWidth:" + width + "\n";
break;
}
lastLength = calculateFontSpace(textSplit[0] + " ", fontSize + "px", font).width;
var lastLine = "";
var lineCount = 0;
Line: for (var i in textSplit) {
if (textSplit.hasOwnProperty(i)) {
lastLine += textSplit[i] + " ";
// Remove last blank
lastLine = lastLine.substr(lastLine.length - 1) == " " ? lastLine.substr(0, lastLine.length - 1) : lastLine;
var key = parseInt(i);
lastLength = 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 = 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.toFixed(2) + " " + lastY.toFixed(2) + " Td\n";
text += "(" + line + ") Tj\n";
// reset X in PDF
text += -startX.toFixed(2) + " 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;
};
/**
* small workaround for calculating the TextMetric approximately
*
* @param text
* @param fontsize
* @returns {TextMetrics} (Has Height and Width)
*/
var calculateFontSpace = function(text, fontSize, fontType) {
fontType = fontType || "helvetica";
var font = scope.internal.getFont(fontType);
var width =
scope.getStringUnitWidth(text, {
font: font,
fontSize: parseFloat(fontSize),
charSpace: 0
}) * parseFloat(fontSize);
var height =
scope.getStringUnitWidth("3", {
font: font,
fontSize: parseFloat(fontSize),
charSpace: 0
}) *
parseFloat(fontSize) *
1.5;
var result = { height: height, width: width };
return result;
};
var acroformPluginTemplate = {
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,
isInitialized: false
};
var annotReferenceCallback = function() {
var fields = scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields;
for (var i in fields) {
if (fields.hasOwnProperty(i)) {
var formObject = 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(scope, formObject);
}
}
}
};
var putForm = function(formObject) {
if (scope.internal.acroformPlugin.printedOut) {
scope.internal.acroformPlugin.printedOut = false;
scope.internal.acroformPlugin.acroFormDictionaryRoot = null;
}
if (!scope.internal.acroformPlugin.acroFormDictionaryRoot) {
initializeAcroForm.call(scope);
}
scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(formObject);
};
/**
* 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
};
scope.annotationPlugin.annotations[scope.internal.getPageInfo(object.page).pageNumber].push(options);
};
// Callbacks
var putCatalogCallback = function() {
// Put reference to AcroForm to DocumentCatalog
if (typeof scope.internal.acroformPlugin.acroFormDictionaryRoot != "undefined") {
// for safety, shouldn't normally be the case
scope.internal.write("/AcroForm " + scope.internal.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
scope.internal.events.unsubscribe(scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID);
delete scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID;
scope.internal.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
scope.internal.newObjectDeferredBegin(scope.internal.acroformPlugin.acroFormDictionaryRoot.objId);
scope.internal.out(scope.internal.acroformPlugin.acroFormDictionaryRoot.getString());
}
var fieldArray = fieldArray || scope.internal.acroformPlugin.acroFormDictionaryRoot.Kids;
for (var i in fieldArray) {
if (fieldArray.hasOwnProperty(i)) {
var key = i;
var form = fieldArray[i];
var oldRect = form.Rect;
if (form.Rect) {
form.Rect = calculateCoordinates.call(this, form.Rect);
}
// Start Writing the Object
scope.internal.newObjectDeferredBegin(form.objId);
var content = form.objId + " 0 obj\n<<\n";
if (typeof form === "object" && typeof form.getContent === "function") {
content += form.getContent();
}
form.Rect = oldRect;
if (form.hasAppearanceStream && !form.appearanceStreamContent) {
// Calculate Appearance
var appearance = calculateAppearanceStream.call(this, form);
content += "/AP << /N " + appearance + " >>\n";
scope.internal.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) {
if (form.appearanceStreamContent.hasOwnProperty(k)) {
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) {
if (value.hasOwnProperty(i)) {
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 (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0))
scope.internal.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 (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0))
scope.internal.acroformPlugin.xForms.push(obj);
}
content += " >>\n";
}
}
// appearance stream is a normal Object..
content += ">>\n";
}
content += ">>\nendobj\n";
scope.internal.out(content);
}
}
if (standardFields) {
createXFormObjectCallback.call(this, scope.internal.acroformPlugin.xForms);
}
};
var createXFormObjectCallback = function(fieldArray) {
for (var i in fieldArray) {
if (fieldArray.hasOwnProperty(i)) {
var key = i;
var form = fieldArray[i];
// Start Writing the Object
scope.internal.newObjectDeferredBegin(form && form.objId);
var content = "";
if (typeof form === "object" && typeof form.getString === "function") {
content = form.getString();
}
scope.internal.out(content);
delete fieldArray[key];
}
}
};
var initializeAcroForm = function() {
if (
this.internal !== undefined &&
(this.internal.acroformPlugin === undefined || this.internal.acroformPlugin.isInitialized === false)
) {
scope = this;
AcroFormField.FieldNum = 0;
this.internal.acroformPlugin = JSON.parse(JSON.stringify(acroformPluginTemplate));
if (this.internal.acroformPlugin.acroFormDictionaryRoot) {
// return;
throw new Error("Exception while creating AcroformDictionary");
}
scaleFactor = scope.internal.scaleFactor;
pageHeight = scope.internal.pageSize.getHeight();
// The Object Number of the AcroForm Dictionary
scope.internal.acroformPlugin.acroFormDictionaryRoot = new AcroFormDictionary();
// add Callback for creating the AcroForm Dictionary
scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID = scope.internal.events.subscribe(
"postPutResources",
AcroFormDictionaryCallback
);
scope.internal.events.subscribe("buildDocument", annotReferenceCallback); // buildDocument
// Register event, that is triggered when the DocumentCatalog is
// written, in order to add /AcroForm
scope.internal.events.subscribe("putCatalog", putCatalogCallback);
// Register event, that creates all Fields
scope.internal.events.subscribe("postPutPages", createFieldCallback);
scope.internal.acroformPlugin.isInitialized = true;
}
};
var arrayToPdfArray = function(array) {
if (Array.isArray(array)) {
var content = " [";
for (var i in array) {
if (array.hasOwnProperty(i)) {
var element = array[i].toString();
content += element;
content += i < array.length - 1 ? " " : "";
}
}
content += "]";
return content;
}
};
var 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
// ##########################
var AcroFormPDFObject = function() {
// The Object ID in the PDF Object Model
// todo
var _objId;
Object.defineProperty(this, "objId", {
get: function() {
if (!_objId) {
_objId = scope.internal.newObjectDeferred();
}
if (!_objId) {
console.log("Couldn't create Object ID");
}
return _objId;
},
configurable: false
});
};
AcroFormPDFObject.prototype.toString = function() {
return this.objId + " 0 R";
};
AcroFormPDFObject.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 += "\nendstream\n";
}
res += "endobj\n";
return res;
};
AcroFormPDFObject.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) {
if (keys.hasOwnProperty(i)) {
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 + " " + arrayToPdfArray(value) + "\n";
} else if (value instanceof AcroFormPDFObject) {
// 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;
};
var AcroFormXObject = function() {
AcroFormPDFObject.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.trim();
},
get: function() {
if (_stream) {
return _stream;
} else {
return null;
}
}
});
};
inherit(AcroFormXObject, AcroFormPDFObject);
// ##### The Objects, the User can Create:
var AcroFormDictionary = function() {
AcroFormPDFObject.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;
};
inherit(AcroFormDictionary, AcroFormPDFObject);
// The Field Object contains the Variables, that every Field needs
// Rectangle for Appearance: lower_left_X, lower_left_Y, width, height
var AcroFormField = function() {
"use strict";
AcroFormPDFObject.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;
}
});
var _F = 4;
Object.defineProperty(this, "F", {
enumerable: true,
set: function(val) {
_F = val;
},
get: function() {
return _F;
}
});
/**
* 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 AcroFormChildClass) {
// In case of a Child from a Radio´Group, you don't
// need a FieldName!!!
return;
}
return "(FieldObject" + AcroFormField.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;
}
});
var _V;
// Defines the default value
Object.defineProperty(this, "V", {
enumerable: true,
configurable: true,
get: function() {
if (!_V) {
return;
}
return _V;
},
set: function(val) {
_V = 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
});
};
inherit(AcroFormField, AcroFormPDFObject);
var AcroFormChoiceField = function() {
AcroFormField.call(this);
// Field Type = Choice Field
this.FT = "/Ch";
// options
this.Opt = [];
this.V = "()";
// Top Index
this.TI = 0;
/**
* Defines, whether the
*
* @type {boolean}
*/
var _combo = false;
Object.defineProperty(this, "combo", {
enumerable: false,
get: function() {
return _combo;
},
set: function(val) {
_combo = val;
}
});
/**
* 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;
};
inherit(AcroFormChoiceField, AcroFormField);
var AcroFormListBox = function() {
AcroFormChoiceField.call(this);
this.combo = false;
};
inherit(AcroFormListBox, AcroFormChoiceField);
var AcroFormComboBox = function() {
AcroFormListBox.call(this);
this.combo = true;
};
inherit(AcroFormComboBox, AcroFormListBox);
var AcroFormEditBox = function() {
AcroFormComboBox.call(this);
this.edit = true;
};
inherit(AcroFormEditBox, AcroFormComboBox);
var AcroFormButton = function() {
AcroFormField.call(this);
this.FT = "/Btn";
// this.hasAnnotation = true;
};
inherit(AcroFormButton, AcroFormField);
var AcroFormPushButton = function() {
AcroFormButton.call(this);
var _pushbutton = true;
Object.defineProperty(this, "pushbutton", {
enumerable: false,
get: function() {
return _pushbutton;
},
set: function(val) {
_pushbutton = val;
}
});
};
inherit(AcroFormPushButton, AcroFormButton);
var AcroFormRadioButton = function() {
AcroFormButton.call(this);
var _radio = true;
Object.defineProperty(this, "radio", {
enumerable: false,
get: function() {
return _radio;
},
set: function(val) {
_radio = val;
}
});
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;
};
inherit(AcroFormRadioButton, AcroFormButton);
/*
* The Child classs of a RadioButton (the radioGroup) -> The single
* Buttons
*/
var AcroFormChildClass = function(parent, name) {
AcroFormField.call(this);
this.Parent = parent;
// todo: set AppearanceType as variable that can be set from the
// outside...
this._AppearanceType = AcroFormAppearance.RadioButton.Circle;
// The Default appearanceType is the Circle
this.appearanceStreamContent = this._AppearanceType.createAppearanceStream(name);
// Set Print in the Annot Flag
this.F = 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;
};
inherit(AcroFormChildClass, AcroFormField);
AcroFormRadioButton.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) {
if (this.__Kids.hasOwnProperty(i)) {
var child = this.__Kids[i];
child.appearanceStreamContent = appearance.createAppearanceStream(child._Name);
child.MK = appearance.createMK();
}
}
};
AcroFormRadioButton.prototype.createOption = function(name) {
var parent = this;
var kidCount = this.__Kids.length;
// Create new Child for RadioGroup
var child = new AcroFormChildClass(parent, name);
// Add to Parent
this.__Kids.push(child);
jsPDFAPI.addField(child);
return child;
};
var AcroFormCheckBox = function() {
AcroFormButton.call(this);
this.appearanceStreamContent = AcroFormAppearance.CheckBox.createAppearanceStream();
this.MK = AcroFormAppearance.CheckBox.createMK();
this.AS = "/On";
this.V = "/On";
};
inherit(AcroFormCheckBox, AcroFormButton);
var AcroFormTextField = function() {
AcroFormField.call(this);
this.DA = AcroFormAppearance.createDefaultAppearanceStream();
this.F = 4;
var _V;
Object.defineProperty(this, "V", {
get: function() {
if (_V) {
return toPdfString(_V);
} else {
return _V;
}
},
enumerable: true,
set: function(val) {
_V = val;
}
});
var _DV;
Object.defineProperty(this, "DV", {
get: function() {
if (_DV) {
return toPdfString(_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;
}
});
/**
* For PDF 1.4
*
* @type {boolean}
*/
var _fileSelect = false;
Object.defineProperty(this, "fileSelect", {
enumerable: false,
get: function() {
return _fileSelect;
},
set: function(val) {
_fileSelect = val;
}
});
/**
* For PDF 1.4
*
* @type {boolean}
*/
var _doNotSpellCheck = false;
Object.defineProperty(this, "doNotSpellCheck", {
enumerable: false,
get: function() {
return _doNotSpellCheck;
},
set: function(val) {
_doNotSpellCheck = val;
}
});
/**
* For PDF 1.4
*
* @type {boolean}
*/
var _doNotScroll = false;
Object.defineProperty(this, "doNotScroll", {
enumerable: false,
get: function() {
return _doNotScroll;
},
set: function(val) {
_doNotScroll = val;
}
});
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;
}
});
};
inherit(AcroFormTextField, AcroFormField);
var AcroFormPasswordField = function() {
AcroFormTextField.call(this);
var _password = true;
Object.defineProperty(this, "password", {
enumerable: false,
get: function() {
return _password;
},
set: function(val) {
_password = val;
}
});
};
inherit(AcroFormPasswordField, AcroFormTextField);
// Contains Methods for creating standard appearances
var AcroFormAppearance = {
CheckBox: {
createAppearanceStream: function() {
var appearance = {
N: {
On: AcroFormAppearance.CheckBox.YesNormal
},
D: {
On: AcroFormAppearance.CheckBox.YesPushDown,
Off: AcroFormAppearance.CheckBox.OffPushDown
}
};
return appearance;
},
/**
* If any other icons are needed, the number between the
* brackets can be changed
*
* @returns {string}
*/
createMK: function() {
return "<< /CA (3)>>";
},
/**
* Returns the standard On Appearance for a CheckBox
*
* @returns {AcroFormXObject}
*/
YesPushDown: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
var zapfDingbatsId = scope.internal.getFont("zapfdingbats", "normal").id;
formObject.Q = 1; // set text-alignment as centered
var calcRes = calculateX(formObject, "3", "ZapfDingbats", 50);
stream.push("0.749023 g");
stream.push(
"0 0 " +
AcroFormAppearance.internal.getWidth(formObject).toFixed(2) +
" " +
AcroFormAppearance.internal.getHeight(formObject).toFixed(2) +
" re"
);
stream.push("f");
stream.push("BMC");
stream.push("q");
stream.push("0 0 1 rg");
stream.push("/" + zapfDingbatsId + " " + calcRes.fontSize.toFixed(2) + " Tf 0 g");
stream.push("BT");
stream.push(calcRes.text);
stream.push("ET");
stream.push("Q");
stream.push("EMC");
xobj.stream = stream.join("\n");
return xobj;
},
YesNormal: function(formObject) {
var xobj = createFormXObject(formObject);
var zapfDingbatsId = scope.internal.getFont("zapfdingbats", "normal").id;
var stream = [];
formObject.Q = 1; // set text-alignment as centered
var height = AcroFormAppearance.internal.getHeight(formObject);
var width = AcroFormAppearance.internal.getWidth(formObject);
var calcRes = calculateX(formObject, "3", "ZapfDingbats", height * 0.9);
stream.push("1 g");
stream.push("0 0 " + width.toFixed(2) + " " + height.toFixed(2) + " re");
stream.push("f");
stream.push("q");
stream.push("0 0 1 rg");
stream.push("0 0 " + (width - 1).toFixed(2) + " " + (height - 1).toFixed(2) + " re");
stream.push("W");
stream.push("n");
stream.push("0 g");
stream.push("BT");
stream.push("/" + zapfDingbatsId + " " + calcRes.fontSize.toFixed(2) + " Tf 0 g");
stream.push(calcRes.text);
stream.push("ET");
stream.push("Q");
xobj.stream = stream.join("\n");
return xobj;
},
/**
* Returns the standard Off Appearance for a CheckBox
*
* @returns {AcroFormXObject}
*/
OffPushDown: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
stream.push("0.749023 g");
stream.push(
"0 0 " +
AcroFormAppearance.internal.getWidth(formObject).toFixed(2) +
" " +
AcroFormAppearance.internal.getHeight(formObject).toFixed(2) +
" re"
);
stream.push("f");
xobj.stream = stream.join("\n");
return xobj;
}
},
RadioButton: {
Circle: {
createAppearanceStream: function(name) {
var appearanceStreamContent = {
D: {
Off: AcroFormAppearance.RadioButton.Circle.OffPushDown
},
N: {}
};
appearanceStreamContent.N[name] = AcroFormAppearance.RadioButton.Circle.YesNormal;
appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Circle.YesPushDown;
return appearanceStreamContent;
},
createMK: function() {
return "<< /CA (l)>>";
},
YesNormal: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
// Make the Radius of the Circle relative to min(height,
// width) of formObject
var DotRadius =
AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject)
? AcroFormAppearance.internal.getWidth(formObject) / 4
: AcroFormAppearance.internal.getHeight(formObject) / 4;
// The Borderpadding...
DotRadius *= 0.9;
var c = AcroFormAppearance.internal.Bezier_C;
/*
* The Following is a Circle created with Bezier-Curves.
*/
stream.push("q");
stream.push(
"1 0 0 1 " +
AcroFormAppearance.internal.getWidth(formObject) / 2 +
" " +
AcroFormAppearance.internal.getHeight(formObject) / 2 +
" cm"
);
stream.push(DotRadius + " 0 m");
stream.push(
DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c"
);
stream.push(
"-" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c"
);
stream.push(
"-" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c"
);
stream.push(
DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c"
);
stream.push("f");
stream.push("Q");
xobj.stream = stream.join("\n");
return xobj;
},
YesPushDown: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
var DotRadius =
AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject)
? AcroFormAppearance.internal.getWidth(formObject) / 4
: AcroFormAppearance.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 = AcroFormAppearance.internal.Bezier_C;
var kc = k * AcroFormAppearance.internal.Bezier_C;
var dc = DotRadius * AcroFormAppearance.internal.Bezier_C;
stream.push("0.749023 g");
stream.push("q");
stream.push(
"1 0 0 1 " +
(AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) +
" " +
(AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) +
" cm"
);
stream.push(k + " 0 m");
stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c");
stream.push("-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c");
stream.push("-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c");
stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c");
stream.push("f");
stream.push("Q");
stream.push("0 g");
stream.push("q");
stream.push(
"1 0 0 1 " +
(AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) +
" " +
(AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) +
" cm"
);
stream.push(DotRadius + " 0 m");
stream.push("" + DotRadius + " " + dc + " " + dc + " " + DotRadius + " 0 " + DotRadius + " c");
stream.push("-" + dc + " " + DotRadius + " -" + DotRadius + " " + dc + " -" + DotRadius + " 0 c");
stream.push("-" + DotRadius + " -" + dc + " -" + dc + " -" + DotRadius + " 0 -" + DotRadius + " c");
stream.push(dc + " -" + DotRadius + " " + DotRadius + " -" + dc + " " + DotRadius + " 0 c");
stream.push("f");
stream.push("Q");
xobj.stream = stream.join("\n");
return xobj;
},
OffPushDown: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
var DotRadius =
AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject)
? AcroFormAppearance.internal.getWidth(formObject) / 4
: AcroFormAppearance.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 = AcroFormAppearance.internal.Bezier_C;
var kc = k * AcroFormAppearance.internal.Bezier_C;
stream.push("0.749023 g");
stream.push("q");
stream.push(
"1 0 0 1 " +
(AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) +
" " +
(AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) +
" cm"
);
stream.push(k + " 0 m");
stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c");
stream.push("-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c");
stream.push("-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c");
stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c");
stream.push("f");
stream.push("Q");
xobj.stream = stream.join("\n");
return xobj;
}
},
Cross: {
/**
* Creates the Actual AppearanceDictionary-References
*
* @param name
* @returns
*/
createAppearanceStream: function(name) {
var appearanceStreamContent = {
D: {
Off: AcroFormAppearance.RadioButton.Cross.OffPushDown
},
N: {}
};
appearanceStreamContent.N[name] = AcroFormAppearance.RadioButton.Cross.YesNormal;
appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Cross.YesPushDown;
return appearanceStreamContent;
},
createMK: function() {
return "<< /CA (8)>>";
},
YesNormal: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
var cross = AcroFormAppearance.internal.calculateCross(formObject);
stream.push("q");
stream.push(
"1 1 " +
(AcroFormAppearance.internal.getWidth(formObject) - 2).toFixed(2) +
" " +
(AcroFormAppearance.internal.getHeight(formObject) - 2).toFixed(2) +
" re"
);
stream.push("W");
stream.push("n");
stream.push(cross.x1.x.toFixed(2) + " " + cross.x1.y.toFixed(2) + " m");
stream.push(cross.x2.x.toFixed(2) + " " + cross.x2.y.toFixed(2) + " l");
stream.push(cross.x4.x.toFixed(2) + " " + cross.x4.y.toFixed(2) + " m");
stream.push(cross.x3.x.toFixed(2) + " " + cross.x3.y.toFixed(2) + " l");
stream.push("s");
stream.push("Q");
xobj.stream = stream.join("\n");
return xobj;
},
YesPushDown: function(formObject) {
var xobj = createFormXObject(formObject);
var cross = AcroFormAppearance.internal.calculateCross(formObject);
var stream = [];
stream.push("0.749023 g");
stream.push(
"0 0 " +
AcroFormAppearance.internal.getWidth(formObject).toFixed(2) +
" " +
AcroFormAppearance.internal.getHeight(formObject).toFixed(2) +
" re"
);
stream.push("f");
stream.push("q");
stream.push(
"1 1 " +
(AcroFormAppearance.internal.getWidth(formObject) - 2).toFixed(2) +
" " +
(AcroFormAppearance.internal.getHeight(formObject) - 2).toFixed(2) +
" re"
);
stream.push("W");
stream.push("n");
stream.push(cross.x1.x.toFixed(2) + " " + cross.x1.y.toFixed(2) + " m");
stream.push(cross.x2.x.toFixed(2) + " " + cross.x2.y.toFixed(2) + " l");
stream.push(cross.x4.x.toFixed(2) + " " + cross.x4.y.toFixed(2) + " m");
stream.push(cross.x3.x.toFixed(2) + " " + cross.x3.y.toFixed(2) + " l");
stream.push("s");
stream.push("Q");
xobj.stream = stream.join("\n");
return xobj;
},
OffPushDown: function(formObject) {
var xobj = createFormXObject(formObject);
var stream = [];
stream.push("0.749023 g");
stream.push(
"0 0 " +
AcroFormAppearance.internal.getWidth(formObject).toFixed(2) +
" " +
AcroFormAppearance.internal.getHeight(formObject).toFixed(2) +
" re"
);
stream.push("f");
xobj.stream = stream.join("\n");
return xobj;
}
}
},
/**
* Returns the standard Appearance
*
* @returns {AcroFormXObject}
*/
createDefaultAppearanceStream: function(formObject) {
// Set Helvetica to Standard Font (size: auto)
// Color: Black
return "/F1 0 Tf 0 g";
}
};
AcroFormAppearance.internal = {
Bezier_C: 0.551915024494,
calculateCross: function(formObject) {
var min = function(x, y) {
return x > y ? y : x;
};
var width = AcroFormAppearance.internal.getWidth(formObject);
var height = AcroFormAppearance.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;
}
};
AcroFormAppearance.internal.getWidth = function(formObject) {
var result = 0;
if (typeof formObject === "object") {
result = scale(formObject.Rect[2]); // (formObject.Rect[2] -
// formObject.Rect[0]) || 0;
}
return result;
};
AcroFormAppearance.internal.getHeight = function(formObject) {
var result = 0;
if (typeof formObject === "object") {
result = scale(formObject.Rect[3]); // (formObject.Rect[1] -
// formObject.Rect[3]) || 0;
}
return result;
};
// Public:
jsPDFAPI.addField = function(fieldObject) {
initializeAcroForm.call(this);
// var opt = parseOptions(fieldObject);
if (fieldObject instanceof AcroFormTextField) {
this.addTextField.call(this, fieldObject);
} else if (fieldObject instanceof AcroFormChoiceField) {
this.addChoiceField.call(this, fieldObject);
} else if (fieldObject instanceof AcroFormButton) {
this.addButton.call(this, fieldObject);
} else if (fieldObject instanceof AcroFormChildClass) {
putForm.call(this, fieldObject);
} else if (fieldObject) {
// try to put..
putForm.call(this, fieldObject);
}
fieldObject.page = scope.internal.getCurrentPageInfo().pageNumber;
return this;
};
/**
* Button FT = Btn
*/
jsPDFAPI.addButton = function(opts) {
initializeAcroForm.call(this);
var options = opts || new AcroFormField();
options.FT = "/Btn";
options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion());
putForm.call(this, options);
};
jsPDFAPI.addTextField = function(opts) {
initializeAcroForm.call(this);
var options = opts || new AcroFormField();
options.FT = "/Tx";
options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion());
// Add field
putForm.call(this, options);
};
jsPDFAPI.addChoiceField = function(opts) {
initializeAcroForm.call(this);
var options = opts || new AcroFormField();
options.FT = "/Ch";
options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion());
// options.hasAnnotation = true;
// Add field
putForm.call(this, options);
};
if (typeof globalObj == "object") {
globalObj["ChoiceField"] = AcroFormChoiceField;
globalObj["ListBox"] = AcroFormListBox;
globalObj["ComboBox"] = AcroFormComboBox;
globalObj["EditBox"] = AcroFormEditBox;
globalObj["Button"] = AcroFormButton;
globalObj["PushButton"] = AcroFormPushButton;
globalObj["RadioButton"] = AcroFormRadioButton;
globalObj["CheckBox"] = AcroFormCheckBox;
globalObj["TextField"] = AcroFormTextField;
globalObj["PasswordField"] = AcroFormPasswordField;
// backwardsCompatibility
globalObj["AcroForm"] = { Appearance: AcroFormAppearance };
}
jsPDFAPI.AcroFormChoiceField = AcroFormChoiceField;
jsPDFAPI.AcroFormListBox = AcroFormListBox;
jsPDFAPI.AcroFormComboBox = AcroFormComboBox;
jsPDFAPI.AcroFormEditBox = AcroFormEditBox;
jsPDFAPI.AcroFormButton = AcroFormButton;
jsPDFAPI.AcroFormPushButton = AcroFormPushButton;
jsPDFAPI.AcroFormRadioButton = AcroFormRadioButton;
jsPDFAPI.AcroFormCheckBox = AcroFormCheckBox;
jsPDFAPI.AcroFormTextField = AcroFormTextField;
jsPDFAPI.AcroFormPasswordField = AcroFormPasswordField;
jsPDFAPI.AcroForm = {
ChoiceField: AcroFormChoiceField,
ListBox: AcroFormListBox,
ComboBox: AcroFormComboBox,
EditBox: AcroFormEditBox,
Button: AcroFormButton,
PushButton: AcroFormPushButton,
RadioButton: AcroFormRadioButton,
CheckBox: AcroFormCheckBox,
TextField: AcroFormTextField,
PasswordField: AcroFormPasswordField
};
})(jsPDF.API, (typeof window !== "undefined" && window) || (typeof global !== "undefined" && global));