/** @preserve
* jsPDF - PDF Document creation from JavaScript
* Version ${versionID} Built on ${builtOn}
* CommitID ${commitID}
*
* Copyright (c) 2015-2018 yWorks GmbH, http://www.yworks.com
* 2015-2018 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX
* 2010-2016 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
* 2010 Aaron Spike, https://github.com/acspike
* 2012 Willow Systems Corporation, willow-systems.com
* 2012 Pablo Hess, https://github.com/pablohess
* 2012 Florian Jenett, https://github.com/fjenett
* 2013 Warren Weckesser, https://github.com/warrenweckesser
* 2013 Youssef Beddad, https://github.com/lifof
* 2013 Lee Driscoll, https://github.com/lsdriscoll
* 2013 Stefan Slonevskiy, https://github.com/stefslon
* 2013 Jeremy Morel, https://github.com/jmorel
* 2013 Christoph Hartmann, https://github.com/chris-rock
* 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
* 2014 James Makes, https://github.com/dollaruw
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 Steven Spungin, https://github.com/Flamenco
* 2014 Kenneth Glassey, https://github.com/Gavvers
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Contributor(s):
* siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
* kim3er, mfo, alnorth, Flamenco
*/
/**
* Creates new jsPDF document object instance.
* @name jsPDF
* @class
* @param orientation {String/Object} Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l") <br />
* Can also be an options object.
* @param unit {String} Measurement unit to be used when coordinates are specified.<br />
* Possible values are "pt" (points), "mm" (Default), "cm", "in" or "px".
* @param format {String/Array} The format of the first page. Can be <ul><li>a0 - a10</li><li>b0 - b10</li><li>c0 - c10</li><li>c0 - c10</li><li>dl</li><li>letter</li><li>government-letter</li><li>legal</li><li>junior-legal</li><li>ledger</li><li>tabloid</li><li>credit-card</li></ul><br />
* Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array , e.g. [595.28, 841.89]
* @returns {jsPDF}
* @description
* If the first parameter (orientation) is an object, it will be interpreted as an object of named parameters
* ```
* {
* orientation: 'p',
* unit: 'mm',
* format: 'a4',
* hotfixes: [] // an array of hotfix strings to enable
* }
* ```
*/
var jsPDF = (function(global) {
"use strict";
var pdfVersion = "1.3",
pageFormats = {
// Size in pt of various paper formats
a0: [2383.94, 3370.39],
a1: [1683.78, 2383.94],
a2: [1190.55, 1683.78],
a3: [841.89, 1190.55],
a4: [595.28, 841.89],
a5: [419.53, 595.28],
a6: [297.64, 419.53],
a7: [209.76, 297.64],
a8: [147.4, 209.76],
a9: [104.88, 147.4],
a10: [73.7, 104.88],
b0: [2834.65, 4008.19],
b1: [2004.09, 2834.65],
b2: [1417.32, 2004.09],
b3: [1000.63, 1417.32],
b4: [708.66, 1000.63],
b5: [498.9, 708.66],
b6: [354.33, 498.9],
b7: [249.45, 354.33],
b8: [175.75, 249.45],
b9: [124.72, 175.75],
b10: [87.87, 124.72],
c0: [2599.37, 3676.54],
c1: [1836.85, 2599.37],
c2: [1298.27, 1836.85],
c3: [918.43, 1298.27],
c4: [649.13, 918.43],
c5: [459.21, 649.13],
c6: [323.15, 459.21],
c7: [229.61, 323.15],
c8: [161.57, 229.61],
c9: [113.39, 161.57],
c10: [79.37, 113.39],
dl: [311.81, 623.62],
letter: [612, 792],
"government-letter": [576, 756],
legal: [612, 1008],
"junior-legal": [576, 360],
ledger: [1224, 792],
tabloid: [792, 1224],
"credit-card": [153, 243]
};
/**
* jsPDF's Internal PubSub Implementation.
* See mrrio.github.io/jsPDF/doc/symbols/PubSub.html
* Backward compatible rewritten on 2014 by
* Diego Casorran, https://github.com/diegocr
*
* @class
* @name PubSub
* @ignore This should not be in the public docs.
*/
function PubSub(context) {
var topics = {};
this.subscribe = function(topic, callback, once) {
if (typeof callback !== "function") {
return false;
}
if (!topics.hasOwnProperty(topic)) {
topics[topic] = {};
}
var id = Math.random().toString(35);
topics[topic][id] = [callback, !!once];
return id;
};
this.unsubscribe = function(token) {
for (var topic in topics) {
if (topics[topic][token]) {
delete topics[topic][token];
return true;
}
}
return false;
};
this.publish = function(topic) {
if (topics.hasOwnProperty(topic)) {
var args = Array.prototype.slice.call(arguments, 1),
idr = [];
for (var id in topics[topic]) {
var sub = topics[topic][id];
try {
sub[0].apply(context, args);
} catch (ex) {
if (global.console) {
console.error("jsPDF PubSub Error", ex.message, ex);
}
}
if (sub[1]) idr.push(id);
}
if (idr.length) idr.forEach(this.unsubscribe);
}
};
}
/**
* @constructor
* @private
*/
function jsPDF(orientation, unit, format, compressPdf) {
var options = {};
if (typeof orientation === "object") {
options = orientation;
orientation = options.orientation;
unit = options.unit || unit;
format = options.format || format;
compressPdf = options.compress || options.compressPdf || compressPdf;
}
// Default options
unit = unit || "mm";
format = format || "a4";
orientation = ("" + (orientation || "P")).toLowerCase();
var format_as_string = ("" + format).toLowerCase(),
compress = !!compressPdf && typeof Uint8Array === "function",
textColor = options.textColor || "0 g",
drawColor = options.drawColor || "0 G",
activeFontSize = options.fontSize || 16,
activeCharSpace = options.charSpace || 0,
R2L = options.R2L || false,
lineHeightProportion = options.lineHeight || 1.15,
lineWidth = options.lineWidth || 0.200025, // 2mm
fileId = "00000000000000000000000000000000",
objectNumber = 2, // 'n' Current object number
outToPages = !1, // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content
offsets = [], // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes.
fonts = {}, // collection of font objects, where key is fontKey - a dynamically created label for a given font.
fontmap = {}, // mapping structure fontName > fontStyle > font key - performance layer. See addFont()
activeFontKey, // will be string representing the KEY of the font as combination of fontName + fontStyle
fontStateStack = [], //
patterns = {}, // collection of pattern objects
patternMap = {}, // see fonts
gStates = {}, // collection of graphic state objects
gStatesMap = {}, // see fonts
activeGState = null,
k, // Scale factor
tmp,
page = 0,
currentPage,
pages = [],
pagesContext = [], // same index as pages and pagedim
pagedim = [],
content = [],
additionalObjects = [],
lineCapID = 0,
lineJoinID = 0,
content_length = 0,
renderTargets = {},
renderTargetMap = {},
renderTargetStack = [],
pageX,
pageY,
pageMatrix, // only used for FormObjects
pageWidth,
pageHeight,
pageMode,
zoomMode,
layoutMode,
creationDate,
documentProperties = {
title: "",
subject: "",
author: "",
keywords: "",
creator: ""
},
API = {},
ApiMode = {
COMPAT: "compat",
ADVANCED: "advanced"
},
apiMode = ApiMode.COMPAT,
events = new PubSub(API),
hotfixes = options.hotfixes || [],
/////////////////////
// Private functions
/////////////////////
generateColorString = function(options) {
var color;
var ch1 = options.ch1;
var ch2 = options.ch2;
var ch3 = options.ch3;
var ch4 = options.ch4;
var precision = options.precision;
var letterArray = options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"];
if (typeof ch1 === "string" && ch1.charAt(0) !== "#") {
var rgbColor = new RGBColor(ch1);
if (rgbColor.ok) {
ch1 = rgbColor.toHex();
}
}
//convert short rgb to long form
if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) {
ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3];
}
if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) {
var hex = parseInt(ch1.substr(1), 16);
ch1 = (hex >> 16) & 255;
ch2 = (hex >> 8) & 255;
ch3 = hex & 255;
}
if (typeof ch2 === "undefined" || (typeof ch4 === "undefined" && (ch1 === ch2 && ch2 === ch3))) {
// Gray color space.
if (typeof ch1 === "string") {
color = ch1 + " " + letterArray[0];
} else {
switch (options.precision) {
case 2:
color = f2(ch1 / 255) + " " + letterArray[0];
break;
case 3:
default:
color = f3(ch1 / 255) + " " + letterArray[0];
}
}
} else if (typeof ch4 === "undefined" || typeof ch4 === "object") {
// assume RGB
if (typeof ch1 === "string") {
color = [ch1, ch2, ch3, letterArray[1]].join(" ");
} else {
switch (options.precision) {
case 2:
color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), letterArray[1]].join(" ");
break;
default:
case 3:
color = [f3(ch1 / 255), f3(ch2 / 255), f3(ch3 / 255), letterArray[1]].join(" ");
}
}
// assume RGBA
if (ch4 && ch4.a === 0) {
//TODO Implement transparency.
//WORKAROUND use white for now
color = ["255", "255", "255", letterArray[1]].join(" ");
}
} else {
// assume CMYK
if (typeof ch1 === "string") {
color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" ");
} else {
switch (options.precision) {
case 2:
color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join(" ");
break;
case 3:
default:
color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join(" ");
}
}
}
return color;
},
convertDateToPDFDate = function(parmDate) {
var padd2 = function(number) {
return ("0" + parseInt(number)).slice(-2);
};
var result = "";
var tzoffset = parmDate.getTimezoneOffset(),
tzsign = tzoffset < 0 ? "+" : "-",
tzhour = Math.floor(Math.abs(tzoffset / 60)),
tzmin = Math.abs(tzoffset % 60),
timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join("");
result = [
"D:",
parmDate.getFullYear(),
padd2(parmDate.getMonth() + 1),
padd2(parmDate.getDate()),
padd2(parmDate.getHours()),
padd2(parmDate.getMinutes()),
padd2(parmDate.getSeconds()),
timeZoneString
].join("");
return result;
},
convertPDFDateToDate = function(parmPDFDate) {
var year = parseInt(parmPDFDate.substr(2, 4), 10);
var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1;
var date = parseInt(parmPDFDate.substr(8, 2), 10);
var hour = parseInt(parmPDFDate.substr(10, 2), 10);
var minutes = parseInt(parmPDFDate.substr(12, 2), 10);
var seconds = parseInt(parmPDFDate.substr(14, 2), 10);
var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10);
var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10);
var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0);
return resultingDate;
},
setCreationDate = function(date) {
var tmpCreationDateString;
var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|\-0[0-9]|\-1[0-1])\'(0[0-9]|[1-5][0-9])\'?$/;
if (typeof date === undefined) {
date = new Date();
}
if (typeof date === "object" && Object.prototype.toString.call(date) === "[object Date]") {
tmpCreationDateString = convertDateToPDFDate(date);
} else if (regexPDFCreationDate.test(date)) {
tmpCreationDateString = date;
} else {
tmpCreationDateString = convertDateToPDFDate(new Date());
}
creationDate = tmpCreationDateString;
return creationDate;
},
getCreationDate = function(type) {
var result = creationDate;
if (type === "jsDate") {
result = convertPDFDateToDate(creationDate);
}
return result;
},
setFileId = function(value) {
value =
value ||
"12345678901234567890123456789012"
.split("")
.map(function() {
return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16));
})
.join("");
fileId = value;
return fileId;
},
getFileId = function() {
return fileId;
},
f2 = function(number) {
return number.toFixed(2); // Ie, %.2f
},
f3 = function(number) {
return number.toFixed(3); // Ie, %.3f
},
// high precision float
hpf = function(number) {
return number.toFixed(16).replace(/0+$/, "");
},
scaleByK = function(coordinate) {
if (apiMode === ApiMode.COMPAT) {
return coordinate * k;
} else if (apiMode === ApiMode.ADVANCED) {
return coordinate;
}
},
transformY = function(y) {
if (apiMode === ApiMode.COMPAT) {
return pageHeight - y;
} else if (apiMode === ApiMode.ADVANCED) {
return y;
}
},
transformScaleY = function(y) {
return scaleByK(transformY(y));
},
padd2 = function(number) {
return ("0" + parseInt(number)).slice(-2);
},
padd2Hex = function(hexString) {
var s = "00" + hexString;
return s.substr(s.length - 2);
},
advancedApiModeTrap = function(methodName) {
if (apiMode !== ApiMode.ADVANCED) {
throw new Error(
methodName +
" is only available in 'advanced' API mode. " +
"You need to call advancedAPI() first."
);
}
},
out = function(string) {
string = typeof string === "string" ? string : string.toString();
if (outToPages) {
/* set by beginPage */
pages[currentPage].push(string);
} else {
// +1 for '\n' that will be used to join 'content'
content_length += string.length + 1;
content.push(string);
}
},
newObject = function() {
// Begin a new object
objectNumber++;
offsets[objectNumber] = content_length;
out(objectNumber + " 0 obj");
return objectNumber;
},
// Does not output the object until after the pages have been output.
// Returns an object containing the objectId and content.
// All pages have been added so the object ID can be estimated to start right after.
// This does not modify the current objectNumber; It must be updated after the newObjects are output.
newAdditionalObject = function() {
var objId = pages.length * 2 + 1;
objId += additionalObjects.length;
var obj = {
objId: objId,
content: ""
};
additionalObjects.push(obj);
return obj;
},
// Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data
newObjectDeferred = function() {
objectNumber++;
offsets[objectNumber] = function() {
return content_length;
};
return objectNumber;
},
newObjectDeferredBegin = function(oid) {
offsets[oid] = content_length;
},
putStream = function(str) {
out("stream");
out(str);
out("endstream");
},
putPages = function() {
var n,
p,
arr,
i,
deflater,
adler32,
adler32cs,
wPt,
hPt,
pageObjectNumbers = [];
adler32cs = global.adler32cs || jsPDF.API.adler32cs;
if (compress && typeof adler32cs === "undefined") {
compress = false;
}
// outToPages = false as set in endDocument(). out() writes to content.
for (n = 1; n <= page; n++) {
pageObjectNumbers.push(newObject());
wPt = (pageWidth = pagedim[n].width) * k;
hPt = (pageHeight = pagedim[n].height) * k;
out("<</Type /Page");
out("/Parent 1 0 R");
out("/Resources 2 0 R");
out("/MediaBox [0 0 " + hpf(wPt) + " " + hpf(hPt) + "]");
// Added for annotation plugin
events.publish("putPage", {
pageNumber: n,
page: pages[n]
});
out("/Contents " + (objectNumber + 1) + " 0 R");
out(">>");
out("endobj");
// Page content
p = pages[n].join("\n");
if (apiMode === ApiMode.ADVANCED) {
// if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again
p += "\nQ";
}
newObject();
if (compress) {
arr = [];
i = p.length;
while (i--) {
arr[i] = p.charCodeAt(i);
}
adler32 = adler32cs.from(p);
deflater = new Deflater(6);
deflater.append(new Uint8Array(arr));
p = deflater.flush();
arr = new Uint8Array(p.length + 6);
arr.set(new Uint8Array([120, 156]));
arr.set(p, 2);
arr.set(
new Uint8Array([adler32 & 0xff, (adler32 >> 8) & 0xff, (adler32 >> 16) & 0xff, (adler32 >> 24) & 0xff]),
p.length + 2
);
p = String.fromCharCode.apply(null, arr);
out("<</Length " + p.length + " /Filter [/FlateDecode]>>");
} else {
out("<</Length " + p.length + ">>");
}
putStream(p);
out("endobj");
}
offsets[1] = content_length;
out("1 0 obj");
out("<</Type /Pages");
var kids = "/Kids [";
for (i = 0; i < page; i++) {
kids += pageObjectNumbers[i] + " 0 R ";
}
out(kids + "]");
out("/Count " + page);
out(">>");
out("endobj");
events.publish("postPutPages");
},
putFont = function(font) {
events.publish("putFont", {
font: font,
out: out,
newObject: newObject
});
if (font.isAlreadyPutted !== true) {
font.objectNumber = newObject();
out("<<");
out("/Type /Font");
out("/BaseFont /" + font.postScriptName);
out("/Subtype /Type1");
if (typeof font.encoding === "string") {
out("/Encoding /" + font.encoding);
}
out("/FirstChar 32");
out("/LastChar 255");
out(">>");
out("endobj");
}
},
putFonts = function() {
for (var fontKey in fonts) {
if (fonts.hasOwnProperty(fontKey)) {
putFont(fonts[fontKey]);
}
}
},
putXObject = function(xObject) {
xObject.objectNumber = newObject();
out("<<");
out("/Type /XObject");
out("/Subtype /Form");
out(
"/BBox [" +
[hpf(xObject.x), hpf(xObject.y), hpf(xObject.x + xObject.width), hpf(xObject.y + xObject.height)].join(
" "
) +
"]"
);
out("/Matrix [" + xObject.matrix.toString() + "]");
// TODO: /Resources
var p = xObject.pages[1].join("\n");
out("/Length " + p.length);
out(">>");
putStream(p);
out("endobj");
},
putXObjects = function() {
for (var xObjectKey in renderTargets) {
if (renderTargets.hasOwnProperty(xObjectKey)) {
putXObject(renderTargets[xObjectKey]);
}
}
},
interpolateAndEncodeRGBStream = function(colors, numberSamples) {
var tValues = [];
var t;
var dT = 1.0 / (numberSamples - 1);
for (t = 0.0; t < 1.0; t += dT) {
tValues.push(t);
}
tValues.push(1.0);
// add first and last control point if not present
if (colors[0].offset != 0.0) {
var c0 = {
offset: 0.0,
color: colors[0].color
};
colors.unshift(c0);
}
if (colors[colors.length - 1].offset != 1.0) {
var c1 = {
offset: 1.0,
color: colors[colors.length - 1].color
};
colors.push(c1);
}
var out = "";
var index = 0;
for (var i = 0; i < tValues.length; i++) {
t = tValues[i];
while (t > colors[index + 1].offset) index++;
var a = colors[index].offset;
var b = colors[index + 1].offset;
var d = (t - a) / (b - a);
var aColor = colors[index].color;
var bColor = colors[index + 1].color;
out +=
padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) +
padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) +
padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16));
}
return out.trim();
},
putShadingPattern = function(pattern, numberSamples) {
/*
Axial patterns shade between the two points specified in coords, radial patterns between the inner
and outer circle.
The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now
interpolated to equidistant samples and written to pdf as a sample (type 0) function.
*/
// The number of color samples that should be used to describe the shading.
// The higher, the more accurate the gradient will be.
numberSamples || (numberSamples = 21);
var funcObjectNumber = newObject();
var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples);
out("<< /FunctionType 0");
out("/Domain [0.0 1.0]");
out("/Size [" + numberSamples + "]");
out("/BitsPerSample 8");
out("/Range [0.0 1.0 0.0 1.0 0.0 1.0]");
out("/Decode [0.0 1.0 0.0 1.0 0.0 1.0]");
out("/Length " + stream.length);
// The stream is Hex encoded
out("/Filter /ASCIIHexDecode");
out(">>");
putStream(stream);
out("endobj");
pattern.objectNumber = newObject();
out("<< /ShadingType " + pattern.type);
out("/ColorSpace /DeviceRGB");
var coords =
"/Coords [" +
hpf(parseFloat(pattern.coords[0])) +
" " + // x1
hpf(parseFloat(pattern.coords[1])) +
" "; // y1
if (pattern.type === 2) {
// axial
coords +=
hpf(parseFloat(pattern.coords[2])) +
" " + // x2
hpf(parseFloat(pattern.coords[3])); // y2
} else {
// radial
coords +=
hpf(parseFloat(pattern.coords[2])) +
" " + // r1
hpf(parseFloat(pattern.coords[3])) +
" " + // x2
hpf(parseFloat(pattern.coords[4])) +
" " + // y2
hpf(parseFloat(pattern.coords[5])); // r2
}
coords += "]";
out(coords);
if (pattern.matrix) {
out("/Matrix [" + pattern.matrix.toString() + "]");
}
out("/Function " + funcObjectNumber + " 0 R");
out("/Extend [true true]");
out(">>");
out("endobj");
},
putTilingPattern = function(pattern) {
var resourcesObjectNumber = newObject();
out("<<");
putResourceDictionary();
out(">>");
out("endobj");
pattern.objectNumber = newObject();
out("<< /Type /Pattern");
out("/PatternType 1"); // tiling pattern
out("/PaintType 1"); // colored tiling pattern
out("/TilingType 1"); // constant spacing
out("/BBox [" + pattern.boundingBox.map(hpf).join(" ") + "]");
out("/XStep " + hpf(pattern.xStep));
out("/YStep " + hpf(pattern.yStep));
out("/Length " + pattern.stream.length);
out("/Resources " + resourcesObjectNumber + " 0 R"); // TODO: resources
pattern.matrix && out("/Matrix [" + pattern.matrix.toString() + "]");
out(">>");
putStream(pattern.stream);
out("endobj");
},
putPatterns = function() {
var patternKey;
for (patternKey in patterns) {
if (patterns.hasOwnProperty(patternKey)) {
if (patterns[patternKey] instanceof API.ShadingPattern) {
putShadingPattern(patterns[patternKey]);
} else if (patterns[patternKey] instanceof API.TilingPattern) {
putTilingPattern(patterns[patternKey]);
}
}
}
},
putGState = function(gState) {
gState.objectNumber = newObject();
out("<<");
for (var p in gState) {
switch (p) {
case "opacity":
out("/ca " + f2(gState[p]));
break;
case "stroke-opacity":
out("/CA " + f2(gState[p]));
break;
}
}
out(">>");
out("endobj");
},
putGStates = function() {
var gStateKey;
for (gStateKey in gStates) {
if (gStates.hasOwnProperty(gStateKey)) {
putGState(gStates[gStateKey]);
}
}
},
putXobjectDict = function() {
for (var xObjectKey in renderTargets) {
if (renderTargets.hasOwnProperty(xObjectKey) && renderTargets[xObjectKey].objectNumber >= 0) {
out("/" + xObjectKey + " " + renderTargets[xObjectKey].objectNumber + " 0 R");
}
}
events.publish("putXobjectDict");
},
putShadingPatternDict = function() {
for (var patternKey in patterns) {
if (
patterns.hasOwnProperty(patternKey) &&
patterns[patternKey] instanceof API.ShadingPattern &&
patterns[patternKey].objectNumber >= 0
) {
out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
}
}
events.publish("putShadingPatternDict");
},
putTilingPatternDict = function() {
for (var patternKey in patterns) {
if (
patterns.hasOwnProperty(patternKey) &&
patterns[patternKey] instanceof API.TilingPattern &&
patterns[patternKey].objectNumber >= 0
) {
out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
}
}
events.publish("putTilingPatternDict");
},
putGStatesDict = function() {
var gStateKey;
for (gStateKey in gStates) {
if (gStates.hasOwnProperty(gStateKey) && gStates[gStateKey].objectNumber >= 0) {
out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R");
}
}
events.publish("putGStateDict");
},
putResourceDictionary = function() {
out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]");
out("/Font <<");
// Do this for each font, the '1' bit is the index of the font
for (var fontKey in fonts) {
if (fonts.hasOwnProperty(fontKey)) {
out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R");
}
}
out(">>");
out("/Shading <<");
putShadingPatternDict();
out(">>");
out("/Pattern <<");
putTilingPatternDict();
out(">>");
out("/ExtGState <<");
putGStatesDict();
out(">>");
out("/XObject <<");
putXobjectDict();
out(">>");
},
putResources = function() {
putFonts();
putGStates();
putXObjects();
putPatterns();
events.publish("putResources");
// Resource dictionary
offsets[2] = content_length;
out("2 0 obj");
out("<<");
putResourceDictionary();
out(">>");
out("endobj");
events.publish("postPutResources");
},
putAdditionalObjects = function() {
events.publish("putAdditionalObjects");
for (var i = 0; i < additionalObjects.length; i++) {
var obj = additionalObjects[i];
offsets[obj.objId] = content_length;
out(obj.objId + " 0 obj");
out(obj.content);
out("endobj");
}
objectNumber += additionalObjects.length;
events.publish("postPutAdditionalObjects");
},
addToFontDictionary = function(fontKey, fontName, fontStyle) {
// this is mapping structure for quick font key lookup.
// returns the KEY of the font (ex: "F1") for a given
// pair of font name and type (ex: "Arial". "Italic")
if (!fontmap.hasOwnProperty(fontName)) {
fontmap[fontName] = {};
}
fontmap[fontName][fontStyle] = fontKey;
},
/**
* FontObject describes a particular font as member of an instnace of jsPDF
*
* It's a collection of properties like 'id' (to be used in PDF stream),
* 'fontName' (font's family name), 'fontStyle' (font's style variant label)
*
* @class
* @public
* @property id {String} PDF-document-instance-specific label assinged to the font.
* @property postScriptName {String} PDF specification full name for the font
* @property encoding {Object} Encoding_name-to-Font_metrics_object mapping.
* @name FontObject
* @ignore This should not be in the public docs.
*/
addFont = function(postScriptName, fontName, fontStyle, encoding) {
var fontKey = "F" + (Object.keys(fonts).length + 1).toString(10),
// This is FontObject
font = (fonts[fontKey] = {
id: fontKey,
postScriptName: postScriptName,
fontName: fontName,
fontStyle: fontStyle,
encoding: encoding,
metadata: {}
});
addToFontDictionary(fontKey, fontName, fontStyle);
events.publish("addFont", font);
return fontKey;
},
addFonts = function() {
var HELVETICA = "helvetica",
TIMES = "times",
COURIER = "courier",
NORMAL = "normal",
BOLD = "bold",
ITALIC = "italic",
BOLD_ITALIC = "bolditalic",
encoding = "StandardEncoding",
ZAPF = "zapfdingbats",
SYMBOL = "symbol",
standardFonts = [
["Helvetica", HELVETICA, NORMAL, "WinAnsiEncoding"],
["Helvetica-Bold", HELVETICA, BOLD, "WinAnsiEncoding"],
["Helvetica-Oblique", HELVETICA, ITALIC, "WinAnsiEncoding"],
["Helvetica-BoldOblique", HELVETICA, BOLD_ITALIC, "WinAnsiEncoding"],
["Courier", COURIER, NORMAL, "WinAnsiEncoding"],
["Courier-Bold", COURIER, BOLD, "WinAnsiEncoding"],
["Courier-Oblique", COURIER, ITALIC, "WinAnsiEncoding"],
["Courier-BoldOblique", COURIER, BOLD_ITALIC, "WinAnsiEncoding"],
["Times-Roman", TIMES, NORMAL, "WinAnsiEncoding"],
["Times-Bold", TIMES, BOLD, "WinAnsiEncoding"],
["Times-Italic", TIMES, ITALIC, "WinAnsiEncoding"],
["Times-BoldItalic", TIMES, BOLD_ITALIC, "WinAnsiEncoding"],
["ZapfDingbats", ZAPF, NORMAL, null],
["Symbol", SYMBOL, NORMAL, null]
];
for (var i = 0, l = standardFonts.length; i < l; i++) {
var fontKey = addFont(standardFonts[i][0], standardFonts[i][1], standardFonts[i][2], standardFonts[i][3]);
// adding aliases for standard fonts, this time matching the capitalization
var parts = standardFonts[i][0].split("-");
addToFontDictionary(fontKey, parts[0], parts[1] || "");
}
events.publish("addFonts", { fonts: fonts, dictionary: fontmap });
},
matrixMult = function(m1, m2) {
return new Matrix(
m1.a * m2.a + m1.b * m2.c,
m1.a * m2.b + m1.b * m2.d,
m1.c * m2.a + m1.d * m2.c,
m1.c * m2.b + m1.d * m2.d,
m1.e * m2.a + m1.f * m2.c + m2.e,
m1.e * m2.b + m1.f * m2.d + m2.f
);
},
Matrix = function(a, b, c, d, e, f) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
};
Matrix.prototype = {
toString: function() {
return [hpf(this.a), hpf(this.b), hpf(this.c), hpf(this.d), hpf(this.e), hpf(this.f)].join(" ");
},
inversed: function() {
var a = this.a,
b = this.b,
c = this.c,
d = this.d,
e = this.e,
f = this.f;
var quot = 1 / (a * d - b * c);
var aInv = d * quot;
var bInv = -b * quot;
var cInv = -c * quot;
var dInv = a * quot;
var eInv = -aInv * e - cInv * f;
var fInv = -bInv * e - dInv * f;
return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv);
}
};
var unitMatrix = new Matrix(1, 0, 0, 1, 0, 0),
// Used (1) to save the current stream state to the XObjects stack and (2) to save completed form
// objects in the xObjects map.
RenderTarget = function() {
this.page = page;
this.currentPage = currentPage;
this.pages = pages.slice(0);
this.pagedim = pagedim.slice(0);
this.pagesContext = pagesContext.slice(0);
this.x = pageX;
this.y = pageY;
this.matrix = pageMatrix;
this.width = pageWidth;
this.height = pageHeight;
this.id = ""; // set by endFormObject()
this.objectNumber = -1; // will be set by putXObject()
};
RenderTarget.prototype = {
restore: function() {
page = this.page;
currentPage = this.currentPage;
pagesContext = this.pagesContext;
pagedim = this.pagedim;
pages = this.pages;
pageX = this.x;
pageY = this.y;
pageMatrix = this.matrix;
pageWidth = this.width;
pageHeight = this.height;
}
};
var beginNewRenderTarget = function(x, y, width, height, matrix) {
// save current state
renderTargetStack.push(new RenderTarget());
// clear pages
page = currentPage = 0;
pages = [];
pageX = x;
pageY = y;
pageMatrix = matrix;
beginPage(width, height);
},
endFormObject = function(key) {
// only add it if it is not already present (the keys provided by the user must be unique!)
if (renderTargetMap[key]) return;
// save the created xObject
var newXObject = new RenderTarget();
var xObjectId = "Xo" + (Object.keys(renderTargets).length + 1).toString(10);
newXObject.id = xObjectId;
renderTargetMap[key] = xObjectId;
renderTargets[xObjectId] = newXObject;
events.publish("addFormObject", newXObject);
// restore state from stack
renderTargetStack.pop().restore();
},
/**
* Adds a new pattern for later use.
* @param {String} key The key by it can be referenced later. The keys must be unique!
* @param {API.Pattern} pattern The pattern
*/
addPattern = function(key, pattern) {
// only add it if it is not already present (the keys provided by the user must be unique!)
if (patternMap[key]) return;
var prefix = pattern instanceof API.ShadingPattern ? "Sh" : "P";
var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10);
pattern.id = patternKey;
patternMap[key] = patternKey;
patterns[patternKey] = pattern;
events.publish("addPattern", pattern);
},
/**
* Adds a new Graphics State. Duplicates are automatically eliminated.
* @param {String} key Might also be null, if no later reference to this gState is needed
* @param {Object} gState The gState object
*/
addGState = function(key, gState) {
// only add it if it is not already present (the keys provided by the user must be unique!)
if (key && gStatesMap[key]) return;
var duplicate = false;
for (var s in gStates) {
if (gStates.hasOwnProperty(s)) {
if (gStates[s].equals(gState)) {
duplicate = true;
break;
}
}
}
if (duplicate) {
gState = gStates[s];
} else {
var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10);
gStates[gStateKey] = gState;
gState.id = gStateKey;
}
// several user keys may point to the same GState object
key && (gStatesMap[key] = gState.id);
events.publish("addGState", gState);
return gState;
},
SAFE = function __safeCall(fn) {
fn.foo = function __safeCallWrapper() {
try {
return fn.apply(this, arguments);
} catch (e) {
var stack = e.stack || "";
if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1];
var m = "Error in function " + stack.split("\n")[0].split("<")[0] + ": " + e.message;
if (global.console) {
global.console.error(m, e);
if (global.alert) alert(m);
} else {
throw new Error(m);
}
}
};
fn.foo.bar = fn;
return fn.foo;
},
to8bitStream = function(text, flags) {
/**
* PDF 1.3 spec:
* "For text strings encoded in Unicode, the first two bytes must be 254 followed by
* 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts
* with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely
* to be a meaningful beginning of a word or phrase.) The remainder of the
* string consists of Unicode character codes, according to the UTF-16 encoding
* specified in the Unicode standard, version 2.0. Commonly used Unicode values
* are represented as 2 bytes per character, with the high-order byte appearing first
* in the string."
*
* In other words, if there are chars in a string with char code above 255, we
* recode the string to UCS2 BE - string doubles in length and BOM is prepended.
*
* HOWEVER!
* Actual *content* (body) text (as opposed to strings used in document properties etc)
* does NOT expect BOM. There, it is treated as a literal GID (Glyph ID)
*
* Because of Adobe's focus on "you subset your fonts!" you are not supposed to have
* a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could
* fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode
* code page. There, however, all characters in the stream are treated as GIDs,
* including BOM, which is the reason we need to skip BOM in content text (i.e. that
* that is tied to a font).
*
* To signal this "special" PDFEscape / to8bitStream handling mode,
* API.text() function sets (unless you overwrite it with manual values
* given to API.text(.., flags) )
* flags.autoencode = true
* flags.noBOM = true
*
* ===================================================================================
* `flags` properties relied upon:
* .sourceEncoding = string with encoding label.
* "Unicode" by default. = encoding of the incoming text.
* pass some non-existing encoding name
* (ex: 'Do not touch my strings! I know what I am doing.')
* to make encoding code skip the encoding step.
* .outputEncoding = Either valid PDF encoding name
* (must be supported by jsPDF font metrics, otherwise no encoding)
* or a JS object, where key = sourceCharCode, value = outputCharCode
* missing keys will be treated as: sourceCharCode === outputCharCode
* .noBOM
* See comment higher above for explanation for why this is important
* .autoencode
* See comment higher above for explanation for why this is important
*/
var i, l, sourceEncoding, encodingBlock, outputEncoding, newtext, isUnicode, ch, bch;
flags = flags || {};
sourceEncoding = flags.sourceEncoding || "Unicode";
outputEncoding = flags.outputEncoding;
// This 'encoding' section relies on font metrics format
// attached to font objects by, among others,
// "Willow Systems' standard_font_metrics plugin"
// see jspdf.plugin.standard_font_metrics.js for format
// of the font.metadata.encoding Object.
// It should be something like
// .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
// .widths = {0:width, code:width, ..., 'fof':divisor}
// .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
if (
(flags.autoencode || outputEncoding) &&
fonts[activeFontKey].metadata &&
fonts[activeFontKey].metadata[sourceEncoding] &&
fonts[activeFontKey].metadata[sourceEncoding].encoding
) {
encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding;
// each font has default encoding. Some have it clearly defined.
if (!outputEncoding && fonts[activeFontKey].encoding) {
outputEncoding = fonts[activeFontKey].encoding;
}
// Hmmm, the above did not work? Let's try again, in different place.
if (!outputEncoding && encodingBlock.codePages) {
outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
}
if (typeof outputEncoding === "string") {
outputEncoding = encodingBlock[outputEncoding];
}
// we want output encoding to be a JS Object, where
// key = sourceEncoding's character code and
// value = outputEncoding's character code.
if (outputEncoding) {
isUnicode = false;
newtext = [];
for (i = 0, l = text.length; i < l; i++) {
ch = outputEncoding[text.charCodeAt(i)];
if (ch) {
newtext.push(String.fromCharCode(ch));
} else {
newtext.push(text[i]);
}
// since we are looping over chars anyway, might as well
// check for residual unicodeness
if (newtext[i].charCodeAt(0) >> 8) {
/* more than 255 */
isUnicode = true;
}
}
text = newtext.join("");
}
}
i = text.length;
// isUnicode may be set to false above. Hence the triple-equal to undefined
while (isUnicode === undefined && i !== 0) {
if (text.charCodeAt(i - 1) >> 8) {
/* more than 255 */
isUnicode = true;
}
i--;
}
if (!isUnicode) {
return text;
}
newtext = flags.noBOM ? [] : [254, 255];
for (i = 0, l = text.length; i < l; i++) {
ch = text.charCodeAt(i);
bch = ch >> 8; // divide by 256
if (bch >> 8) {
/* something left after dividing by 256 second time */
throw new Error(
"Character at position " + i + " of string '" + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE"
);
}
newtext.push(bch);
newtext.push(ch - (bch << 8));
}
return String.fromCharCode.apply(undefined, newtext);
},
pdfEscape = function(text, flags) {
/**
* Replace '/', '(', and ')' with pdf-safe versions
*
* Doing to8bitStream does NOT make this PDF display unicode text. For that
* we also need to reference a unicode font and embed it - royal pain in the rear.
*
* There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
* which JavaScript Strings are happy to provide. So, while we still cannot display
* 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
* 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
* is still parseable.
* This will allow immediate support for unicode in document properties strings.
*/
return to8bitStream(text, flags)
.replace(/\\/g, "\\\\")
.replace(/\(/g, "\\(")
.replace(/\)/g, "\\)");
},
putInfo = function() {
out("/Producer (jsPDF " + jsPDF.version + ")");
for (var key in documentProperties) {
if (documentProperties.hasOwnProperty(key) && documentProperties[key]) {
out("/" + key.substr(0, 1).toUpperCase() + key.substr(1) + " (" + pdfEscape(documentProperties[key]) + ")");
}
}
out("/CreationDate (" + creationDate + ")");
},
putCatalog = function() {
out("/Type /Catalog");
out("/Pages 1 0 R");
// PDF13ref Section 7.2.1
if (!zoomMode) zoomMode = "fullwidth";
switch (zoomMode) {
case "fullwidth":
out("/OpenAction [3 0 R /FitH null]");
break;
case "fullheight":
out("/OpenAction [3 0 R /FitV null]");
break;
case "fullpage":
out("/OpenAction [3 0 R /Fit]");
break;
case "original":
out("/OpenAction [3 0 R /XYZ null null 1]");
break;
default:
var pcn = "" + zoomMode;
if (pcn.substr(pcn.length - 1) === "%") zoomMode = parseInt(zoomMode) / 100;
if (typeof zoomMode === "number") {
out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]");
}
}
if (!layoutMode) layoutMode = "continuous";
switch (layoutMode) {
case "continuous":
out("/PageLayout /OneColumn");
break;
case "single":
out("/PageLayout /SinglePage");
break;
case "two":
case "twoleft":
out("/PageLayout /TwoColumnLeft");
break;
case "tworight":
out("/PageLayout /TwoColumnRight");
break;
}
if (pageMode) {
/**
* A name object specifying how the document should be displayed when opened:
* UseNone : Neither document outline nor thumbnail images visible -- DEFAULT
* UseOutlines : Document outline visible
* UseThumbs : Thumbnail images visible
* FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible
*/
out("/PageMode /" + pageMode);
}
events.publish("putCatalog");
},
putTrailer = function() {
out("/Size " + (objectNumber + 1));
out("/Root " + objectNumber + " 0 R");
out("/Info " + (objectNumber - 1) + " 0 R");
out("/ID [ <" + fileId + "> <" + fileId + "> ]");
},
beginPage = function(width, height) {
outToPages = true;
pages[++page] = [];
pagedim[page] = {
width: Number(width) || pageWidth,
height: Number(height) || pageHeight
};
pagesContext[page] = {};
_setPage(page);
},
_addPage = function(width, height) {
// Dimensions are stored as user units and converted to points on output
var orientation = typeof height === "string" && height.toLowerCase();
if (typeof width === "string") {
var format = width.toLowerCase();
if (pageFormats.hasOwnProperty(format)) {
width = pageFormats[format][0] / k;
height = pageFormats[format][1] / k;
}
}
if (Array.isArray(width)) {
height = width[1];
width = width[0];
}
if (orientation) {
switch (orientation.substr(0, 1)) {
case "l":
if (height > width) orientation = "s";
break;
case "p":
if (width > height) orientation = "s";
break;
}
if (orientation === "s") {
tmp = width;
width = height;
height = tmp;
}
}
beginPage(width, height);
// Set line width
out(hpf(lineWidth * k) + " w");
// Set draw color
out(drawColor);
// resurrecting non-default line caps, joins
if (lineCapID !== 0) {
out(lineCapID + " J");
}
if (lineJoinID !== 0) {
out(lineJoinID + " j");
}
events.publish("addPage", {
pageNumber: page
});
},
_deletePage = function(n) {
if (n > 0 && n <= page) {
pages.splice(n, 1);
pagedim.splice(n, 1);
page--;
if (currentPage > page) {
currentPage = page;
}
this.setPage(currentPage);
}
},
_setPage = function(n) {
if (n > 0 && n <= page) {
currentPage = n;
pageWidth = pagedim[n].width;
pageHeight = pagedim[n].height;
}
},
/**
* Returns a document-specific font key - a label assigned to a
* font name + font type combination at the time the font was added
* to the font inventory.
*
* Font key is used as label for the desired font for a block of text
* to be added to the PDF document stream.
* @private
* @function
* @param {String} fontName can be undefined on "falthy" to indicate "use current"
* @param {String} fontStyle can be undefined on "falthy" to indicate "use current"
* @returns {String} Font key.
*/
getFont = function(fontName, fontStyle, options) {
var key = undefined,
originalFontName,
fontNameLowerCase;
options = options || {};
fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName;
fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle;
fontNameLowerCase = fontName.toLowerCase();
if (fontmap[fontNameLowerCase] !== undefined && fontmap[fontNameLowerCase][fontStyle] !== undefined) {
key = fontmap[fontNameLowerCase][fontStyle];
} else if (fontmap[fontName] !== undefined && fontmap[fontName][fontStyle] !== undefined) {
key = fontmap[fontName][fontStyle];
} else {
if (options.disableWarning === false) {
console.warn(
"Unable to look up font label for font '" +
fontName +
"', '" +
fontStyle +
"'. Refer to getFontList() for available fonts."
);
}
}
if (!key && !options.noFallback) {
key = fontmap["times"][fontStyle];
if (key == null) {
key = fontmap["times"]["normal"];
}
}
return key;
},
buildDocument = function() {
outToPages = false; // switches out() to content
objectNumber = 2;
content_length = 0;
content = [];
offsets = [];
additionalObjects = [];
// Added for AcroForm
events.publish("buildDocument");
// putHeader()
out("%PDF-" + pdfVersion);
out("%\xBA\xDF\xAC\xE0");
putPages();
// Must happen after putPages
// Modifies current object Id
putAdditionalObjects();
putResources();
// Info
newObject();
out("<<");
putInfo();
out(">>");
out("endobj");
// Catalog
newObject();
out("<<");
putCatalog();
out(">>");
out("endobj");
// Cross-ref
var o = content_length,
i,
p = "0000000000";
out("xref");
out("0 " + (objectNumber + 1));
out(p + " 65535 f ");
for (i = 1; i <= objectNumber; i++) {
var offset = offsets[i];
if (typeof offset === "function") {
out((p + offsets[i]()).slice(-10) + " 00000 n ");
} else {
out((p + offsets[i]).slice(-10) + " 00000 n ");
}
}
// Trailer
out("trailer");
out("<<");
putTrailer();
out(">>");
out("startxref");
out("" + o);
out("%%EOF");
outToPages = true;
return content.join("\n");
},
getStyle = function(style) {
// see path-painting operators in PDF spec
// The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation
// was "n" (none). Although this has nothing to do with transforms, we should use the API switch here.
var op = apiMode === ApiMode.COMPAT ? "S" : "n";
if (style === "D") {
op = "S"; // stroke
} else if (style === "F") {
op = "f"; // fill
} else if (style === "FD" || style === "DF") {
op = "B"; // both
} else if (style === "f" || style === "f*" || style === "B" || style === "B*") {
/*
Allow direct use of these PDF path-painting operators:
- f fill using nonzero winding number rule
- f* fill using even-odd rule
- B fill then stroke with fill using non-zero winding number rule
- B* fill then stroke with fill using even-odd rule
*/
op = style;
}
return op;
},
// puts the style for the previously drawn path. If a patternKey is provided, the pattern is used to fill
// the path. Use patternMatrix to transform the pattern to rhe right location.
putStyle = function(style, patternKey, patternData) {
if (style === null || (apiMode === ApiMode.ADVANCED && style === undefined)) {
return;
}
style = getStyle(style);
// stroking / filling / both the path
if (!patternKey) {
out(style);
return;
}
if (!patternData) {
patternData = { matrix: unitMatrix };
}
if (patternData instanceof Matrix) {
patternData = { matrix: patternData };
}
patternData.key = patternKey;
patternData || (patternData = unitMatrix);
fillWithPattern(patternData, style);
},
fillWithPattern = function(patternData, style) {
var patternId = patternMap[patternData.key];
var pattern = patterns[patternId];
if (pattern instanceof API.ShadingPattern) {
out("q");
out(clipRuleFromStyle(style));
if (pattern.gState) {
API.setGState(pattern.gState);
}
out(patternData.matrix.toString() + " cm");
out("/" + patternId + " sh");
out("Q");
} else if (pattern instanceof API.TilingPattern) {
// pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation,
// so we must flip them
var matrix = new Matrix(1, 0, 0, -1, 0, pageHeight);
if (patternData.matrix) {
matrix = matrixMult(patternData.matrix || unitMatrix, matrix);
// we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances
// for each use
patternId = pattern.createClone(
patternData.key,
patternData.boundingBox,
patternData.xStep,
patternData.yStep,
matrix
).id;
}
out("q");
out("/Pattern cs");
out("/" + patternId + " scn");
if (pattern.gState) {
API.setGState(pattern.gState);
}
out(style);
out("Q");
}
},
clipRuleFromStyle = function(style) {
switch (style) {
case "f":
case "F":
return "W n";
case "f*":
return "W* n";
case "B":
return "W S";
case "B*":
return "W* S";
// these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern
// and "n"/"S" as style would still fill/fill and stroke the path)
case "S":
return "W S";
case "n":
return "W n";
}
},
getArrayBuffer = function() {
var data = buildDocument(),
len = data.length,
ab = new ArrayBuffer(len),
u8 = new Uint8Array(ab);
while (len--) u8[len] = data.charCodeAt(len);
return ab;
},
getBlob = function() {
return new Blob([getArrayBuffer()], {
type: "application/pdf"
});
},
/**
* Generates the PDF document.
*
* If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
*
* @param {String} type A string identifying one of the possible output types.
* @param {Object} options An object providing some additional signalling to PDF generator.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name output
*/
output = SAFE(function(type, options) {
var datauri =
("" + type).substr(0, 6) === "dataur" ? "data:application/pdf;base64," + btoa(buildDocument()) : 0;
switch (type) {
case undefined:
return buildDocument();
case "save":
if (typeof navigator === "object" && navigator.getUserMedia) {
if (global.URL === undefined || global.URL.createObjectURL === undefined) {
return API.output("dataurlnewwindow");
}
}
saveAs(getBlob(), options);
if (typeof saveAs.unload === "function") {
if (global.setTimeout) {
setTimeout(saveAs.unload, 911);
}
}
break;
case "arraybuffer":
return getArrayBuffer();
case "blob":
return getBlob();
case "bloburi":
case "bloburl":
// User is responsible of calling revokeObjectURL
return (global.URL && global.URL.createObjectURL(getBlob())) || void 0;
case "datauristring":
case "dataurlstring":
return datauri;
case "dataurlnewwindow":
var nW = global.open(datauri);
if (nW || typeof safari === "undefined") return nW;
/* pass through */
case "datauri":
case "dataurl":
return (global.document.location.href = datauri);
default:
throw new Error('Output type "' + type + '" is not supported.');
}
// @TODO: Add different output options
}),
/**
* Used to see if a supplied hotfix was requested when the pdf instance was created.
* @param {String} hotfixName - The name of the hotfix to check.
* @returns {boolean}
*/
hasHotfix = function(hotfixName) {
return Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1;
};
switch (unit) {
case "pt":
k = 1;
break;
case "mm":
k = 72 / 25.4000508;
break;
case "cm":
k = 72 / 2.54000508;
break;
case "in":
k = 72;
break;
case "px":
if (hasHotfix("px_scaling") == true) {
k = 72 / 96;
} else {
k = 96 / 72;
}
break;
case "pc":
k = 12;
break;
case "em":
k = 12;
break;
case "ex":
k = 6;
break;
default:
throw "Invalid unit: " + unit;
}
setCreationDate();
setFileId();
//---------------------------------------
// Public API
/**
* Object exposing internal API to plugins
* @public
*/
API.internal = {
pdfEscape: pdfEscape,
getStyle: getStyle,
/**
* Returns {FontObject} describing a particular font.
* @public
* @function
* @param {String} fontName (Optional) Font's family name
* @param {String} fontStyle (Optional) Font's style variation name (Example:"Italic")
* @returns {FontObject}
*/
getFont: function() {
return fonts[getFont.apply(API, arguments)];
},
getFontSize: function() {
return activeFontSize;
},
getCharSpace: function() {
return activeCharSpace;
},
getTextColor: function getTextColor() {
var colorEncoded = textColor.split(" ");
if (colorEncoded.length === 2 && colorEncoded[1] === "g") {
// convert grayscale value to rgb so that it can be converted to hex for consistency
var floatVal = parseFloat(colorEncoded[0]);
colorEncoded = [floatVal, floatVal, floatVal, "r"];
}
var colorAsHex = "#";
for (var i = 0; i < 3; i++) {
colorAsHex += ("0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16)).slice(-2);
}
return colorAsHex;
},
getLineHeight: function() {
return activeFontSize * lineHeightProportion;
},
write: function(string1 /*, string2, string3, etc */) {
out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, " "));
},
getCoordinateString: function(value) {
return hpf(scaleByK(value));
},
getVerticalCoordinateString: function(value) {
return hpf(transformScaleY(value));
},
collections: {},
newObject: newObject,
newAdditionalObject: newAdditionalObject,
newObjectDeferred: newObjectDeferred,
newObjectDeferredBegin: newObjectDeferredBegin,
putStream: putStream,
events: events,
// ratio that you use in multiplication of a given "size" number to arrive to 'point'
// units of measurement.
// scaleFactor is set at initialization of the document and calculated against the stated
// default measurement units for the document.
// If default is "mm", k is the number that will turn number in 'mm' into 'points' number.
// through multiplication.
scaleFactor: k,
pageSize: {
getWidth: function() {
return pageWidth;
},
getHeight: function() {
return pageHeight;
}
},
output: function(type, options) {
return output(type, options);
},
getNumberOfPages: function() {
return pages.length - 1;
},
pages: pages,
out: out,
f2: f2,
getPageInfo: function(pageNumberOneBased) {
var objId = (pageNumberOneBased - 1) * 2 + 3;
return {
objId: objId,
pageNumber: pageNumberOneBased,
pageContext: pagesContext[pageNumberOneBased]
};
},
getCurrentPageInfo: function() {
var objId = (currentPage - 1) * 2 + 3;
return {
objId: objId,
pageNumber: currentPage,
pageContext: pagesContext[currentPage]
};
},
getPDFVersion: function() {
return pdfVersion;
},
hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them.
};
function advancedAPI() {
// prepend global change of basis matrix
// (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix
// that does this job for us (however, texts, images and similar objects must be drawn bottom up))
this.saveGraphicsState();
out(new Matrix(k, 0, 0, -k, 0, pageHeight * k).toString() + " cm");
this.setFontSize(this.getFontSize() / k);
apiMode = ApiMode.ADVANCED;
}
function compatAPI() {
this.restoreGraphicsState();
apiMode = ApiMode.COMPAT;
}
/**
* @callback ApiSwitchBody
* @param {jsPDF} pdf
*/
/**
* For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual
* screen coordinates and the PDF coordinate system.
* - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms
* - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might
* not support this mode, though.
* Initial mode is "compat".
*
* You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to
* the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}.
*
* Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the
* callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject}
* or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode.
*
* @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
* The API mode will be switched back automatically afterwards.
* @returns {jsPDF}
* @methodOf jsPDF#
* @name advancedAPI
*/
API.advancedAPI = function(body) {
var doSwitch = apiMode === ApiMode.COMPAT;
if (doSwitch) {
advancedAPI.call(this);
}
if (typeof body !== "function") {
return this;
}
body(this);
if (doSwitch) {
compatAPI.call(this);
}
return this;
};
/**
* Switches to "compat" API mode. See {@link advancedAPI} for more details.
*
* @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
* The API mode will be switched back automatically afterwards.
* @return {jsPDF}
* @methodOf jsPDF#
* @name compatApi
*/
API.compatAPI = function(body) {
var doSwitch = apiMode === ApiMode.ADVANCED;
if (doSwitch) {
compatAPI.call(this);
}
if (typeof body !== "function") {
return this;
}
body(this);
if (doSwitch) {
advancedAPI.call(this);
}
return this;
};
/**
* @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}.
* @methodOf jsPDF#
* @name isAdvancedAPI
*/
API.isAdvancedAPI = function() {
return apiMode === ApiMode.ADVANCED;
};
/**
* Inserts a debug comment into the pdf.
* @param {String} text
* @returns {jsPDF}
* @methodOf jsPDF#
* @name comment
*/
API.comment = function(text) {
out("#" + text);
return this;
};
/**
* An object representing a pdf graphics state.
* @param parameters A parameter object that contains all properties this graphics state wants to set.
* Supported are: opacity, stroke-opacity
* @constructor
*/
API.GState = function(parameters) {
var supported = "opacity,stroke-opacity".split(",");
for (var p in parameters) {
if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) {
this[p] = parameters[p];
}
}
this.id = ""; // set by addGState()
this.objectNumber = -1; // will be set by putGState()
};
API.GState.prototype.equals = function equals(other) {
var ignore = "id,objectNumber,equals";
if (!other || typeof other !== typeof this) return false;
var count = 0;
for (var p in this) {
if (ignore.indexOf(p) >= 0) continue;
if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false;
if (this[p] !== other[p]) return false;
count++;
}
for (var p in other) {
if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--;
}
return count === 0;
};
/**
* Adds a new {@link GState} for later use. See {@link setGState}.
* @param {String} key
* @param {GState} gState
* @function
* @returns {jsPDF}
*
* @methodOf jsPDF#
* @name addGState
*/
API.addGState = function(key, gState) {
addGState(key, gState);
return this;
};
/**
* Adds (and transfers the focus to) new page to the PDF document.
* @function
* @returns {jsPDF}
*
* @methodOf jsPDF#
* @name setPage
* @param {Number} page Switch the active page to the page number specified
* @example
* doc = jsPDF()
* doc.addPage()
* doc.addPage()
* doc.text('I am on page 3', 10, 10)
* doc.setPage(1)
* doc.text('I am on page 1', 10, 10)
*/
API.addPage = function() {
_addPage.apply(this, arguments);
return this;
};
API.setPage = function() {
_setPage.apply(this, arguments);
return this;
};
API.insertPage = function(beforePage) {
this.addPage();
this.movePage(currentPage, beforePage);
return this;
};
API.movePage = function(targetPage, beforePage) {
var tmpPagesContext, tmpPagedim, tmpPages, i;
if (targetPage > beforePage) {
tmpPages = pages[targetPage];
tmpPagedim = pagedim[targetPage];
tmpPagesContext = pagesContext[targetPage];
for (i = targetPage; i > beforePage; i--) {
pages[i] = pages[i - 1];
pagedim[i] = pagedim[i - 1];
pagesContext[i] = pagesContext[i - 1];
}
pages[beforePage] = tmpPages;
pagedim[beforePage] = tmpPagedim;
pagesContext[beforePage] = tmpPagesContext;
this.setPage(beforePage);
} else if (targetPage < beforePage) {
tmpPages = pages[targetPage];
tmpPagedim = pagedim[targetPage];
tmpPagesContext = pagesContext[targetPage];
for (i = targetPage; i < beforePage; i++) {
pages[i] = pages[i + 1];
pagedim[i] = pagedim[i + 1];
pagesContext[i] = pagesContext[i + 1];
}
pages[beforePage] = tmpPages;
pagedim[beforePage] = tmpPagedim;
pagesContext[beforePage] = tmpPagesContext;
this.setPage(beforePage);
}
return this;
};
API.deletePage = function() {
_deletePage.apply(this, arguments);
return this;
};
API.setCreationDate = function(date) {
setCreationDate(date);
return this;
};
API.getCreationDate = function(type) {
return getCreationDate(type);
};
API.setFileId = function(value) {
setFileId(value);
return this;
};
API.getFileId = function() {
return getFileId();
};
/**
* Set the display mode options of the page like zoom and layout.
*
* @param {integer|String} zoom You can pass an integer or percentage as
* a string. 2 will scale the document up 2x, '200%' will scale up by the
* same amount. You can also set it to 'fullwidth', 'fullheight',
* 'fullpage', or 'original'.
*
* Only certain PDF readers support this, such as Adobe Acrobat
*
* @param {String} layout Layout mode can be: 'continuous' - this is the
* default continuous scroll. 'single' - the single page mode only shows one
* page at a time. 'twoleft' - two column left mode, first page starts on
* the left, and 'tworight' - pages are laid out in two columns, with the
* first page on the right. This would be used for books.
* @param {String} pmode 'UseOutlines' - it shows the
* outline of the document on the left. 'UseThumbs' - shows thumbnails along
* the left. 'FullScreen' - prompts the user to enter fullscreen mode.
*
* @function
* @returns {jsPDF}
* @name setDisplayMode
* @methodOf jsPDF#
*/
API.setDisplayMode = function(zoom, layout, pmode) {
zoomMode = zoom;
layoutMode = layout;
pageMode = pmode;
var validPageModes = [undefined, null, "UseNone", "UseOutlines", "UseThumbs", "FullScreen"];
if (validPageModes.indexOf(pmode) == -1) {
throw new Error(
'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + pmode + '" is not recognized.'
);
}
return this;
};
/**
* Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState}
* later. Here, the general pdf graphics state is meant, also including the current transformation matrix,
* fill and stroke colors etc.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name saveGraphicsState
*/
API.saveGraphicsState = function() {
out("q");
// as we cannot set font key and size independently we must keep track of both
fontStateStack.push({
key: activeFontKey,
size: activeFontSize,
color: textColor
});
return this;
};
/**
* Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack").
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name restoreGraphicsState
*/
API.restoreGraphicsState = function() {
out("Q");
// restore previous font state
var fontState = fontStateStack.pop();
activeFontKey = fontState.key;
activeFontSize = fontState.size;
textColor = fontState.color;
activeGState = null;
return this;
};
/**
* Appends this matrix to the left of all previously applied matrices.
*
* Only available in "advanced" API mode.
*
* @param {Matrix} matrix
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setCurrentTransformationMatrix
*/
API.setCurrentTransformationMatrix = function(matrix) {
advancedApiModeTrap("setCurrentTransformationMatrix()");
out(matrix.toString() + " cm");
return this;
};
/**
* Starts a new pdf form object, which means that all conseequent draw calls target a new independent object
* until {@link endFormObject} is called. The created object can be referenced and drawn later using
* {@link doFormObject}. Nested form objects are possible.
* x, y, width, height set the bounding box that is used to clip the content.
*
* Only available in "advanced" API mode.
*
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to
* the parent's.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name beginFormObject
*/
API.beginFormObject = function(x, y, width, height, matrix) {
advancedApiModeTrap("beginFormObject()");
// The user can set the output target to a new form object. Nested form objects are possible.
// Currently, they use the resource dictionary of the surrounding stream. This should be changed, as
// the PDF-Spec states:
// "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which
// they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions,
// form XObjects may be independent of the content streams in which they appear, and this is strongly
// recommended although not required"
beginNewRenderTarget(x, y, width, height, matrix);
return this;
};
/**
* Completes and saves the form object. Only available in "advanced" API mode.
* @param {String} key The key by which this form object can be referenced.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name endFormObject
*/
API.endFormObject = function(key) {
advancedApiModeTrap("endFormObject()");
endFormObject(key);
return this;
};
/**
* Draws the specified form object by referencing to the respective pdf XObject created with
* {@link API.beginFormObject} and {@link endFormObject}.
* The location is determined by matrix.
*
* Only available in "advanced" API mode.
*
* @param {String} key The key to the form object.
* @param {Matrix} matrix The matrix applied before drawing the form object.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name doFormObject
*/
API.doFormObject = function(key, matrix) {
advancedApiModeTrap("doFormObject()");
var xObject = renderTargets[renderTargetMap[key]];
out("q");
out(matrix.toString() + " cm");
out("/" + xObject.id + " Do");
out("Q");
return this;
};
/**
* Returns the form object specified by key.
* @param key {String}
* @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}}
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name getFormObject
*/
API.getFormObject = function(key) {
var xObject = renderTargets[renderTargetMap[key]];
return {
x: xObject.x,
y: xObject.y,
width: xObject.width,
height: xObject.height,
matrix: xObject.matrix
};
};
/**
* A matrix object for 2D homogenous transformations:
* | a b 0 |
* | c d 0 |
* | e f 1 |
* pdf multiplies matrices righthand: v' = v x m1 x m2 x ...
* @param {number} a
* @param {number} b
* @param {number} c
* @param {number} d
* @param {number} e
* @param {number} f
* @constructor
*/
API.Matrix = Matrix;
/**
* Multiplies two matrices. (see {@link Matrix})
* @param {Matrix} m1
* @param {Matrix} m2
* @methodOf jsPDF#
* @name matrixMult
*/
API.matrixMult = matrixMult;
/**
* The unit matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)).
* @type {Matrix}
* @fieldOf jsPDF#
* @name unitMatrix
*/
API.unitMatrix = unitMatrix;
var Pattern = function(gState, matrix) {
this.gState = gState;
this.matrix = matrix;
this.id = ""; // set by addPattern()
this.objectNumber = -1; // will be set by putPattern()
};
/**
* A pattern describing a shading pattern.
*
* Only available in "advanced" API mode.
*
* @param {String} type One of "axial" or "radial"
* @param {Array<Number>} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points
* or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle.
* @param {Array<Object>} colors An array of objects with the fields "offset" and "color". "offset" describes
* the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255].
* @param {GState=} gState An additional graphics state that gets applied to the pattern (optional).
* @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
* and the use coordinate system (optional).
* @constructor
* @extends API.Pattern
*/
API.ShadingPattern = function(type, coords, colors, gState, matrix) {
advancedApiModeTrap("ShadingPattern");
// see putPattern() for information how they are realized
this.type = type === "axial" ? 2 : 3;
this.coords = coords;
this.colors = colors;
Pattern.call(this, gState, matrix);
};
/**
* A PDF Tiling pattern.
*
* Only available in "advanced" API mode.
*
* @param {Array.<Number>} boundingBox The bounding box at which one pattern cell gets clipped.
* @param {Number} xStep Horizontal spacing between pattern cells.
* @param {Number} yStep Vertical spacing between pattern cells.
* @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional).
* @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
* and the use coordinate system (optional).
* @constructor
* @extends API.Pattern
*/
API.TilingPattern = function(boundingBox, xStep, yStep, gState, matrix) {
advancedApiModeTrap("TilingPattern");
this.boundingBox = boundingBox;
this.xStep = xStep;
this.yStep = yStep;
this.stream = ""; // set by endTilingPattern();
this.cloneIndex = 0;
Pattern.call(this, gState, matrix);
};
API.TilingPattern.prototype = {
createClone: function(patternKey, boundingBox, xStep, yStep, matrix) {
var clone = new API.TilingPattern(
boundingBox || this.boundingBox,
xStep || this.xStep,
yStep || this.yStep,
this.gState,
matrix || this.matrix
);
clone.stream = this.stream;
var key = patternKey + "$$" + this.cloneIndex++ + "$$";
addPattern(key, clone);
return clone;
}
};
/**
* Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode.
* @param {String} key
* @param {Pattern} pattern
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name addPattern
*/
API.addShadingPattern = function(key, pattern) {
advancedApiModeTrap("addShadingPattern()");
addPattern(key, pattern);
return this;
};
/**
* Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern}
* gets called. Only available in "advanced" API mode.
* @param {API.Pattern} pattern
* @methodOf jsPDF#
* @name beginTilingPattern
*/
API.beginTilingPattern = function(pattern) {
advancedApiModeTrap("beginTilingPattern()");
beginNewRenderTarget(
pattern.boundingBox[0],
pattern.boundingBox[1],
pattern.boundingBox[2] - pattern.boundingBox[0],
pattern.boundingBox[3] - pattern.boundingBox[1],
pattern.matrix
);
};
/**
* Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called.
*
* Only available in "advanced" API mode.
*
* @param {string} key A unique key that is used to reference this pattern at later use.
* @param {API.Pattern} pattern The pattern to end.
* @methodOf jsPDF#
* @name endTilingPattern
*/
API.endTilingPattern = function(key, pattern) {
advancedApiModeTrap("endTilingPattern()");
// retrieve the stream
pattern.stream = pages[currentPage].join("\n");
addPattern(key, pattern);
events.publish("endTilingPattern", pattern);
// restore state from stack
renderTargetStack.pop().restore();
};
/**
* Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings.
*
* @function
* @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down
* per font, spacing settings declared before this call.
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Object} options Collection of settings signalling how the text must be encoded. Defaults are sane. If you
* think you want to pass some flags, you likely can read the source.
* @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the
* anchor set by x and y.
*
* If it is a Matrix, this matrix gets directly applied to the text, which allows shearing
* effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this
* matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a
* DIFFERENT result. A matrix is only allowed in "advanced" API mode.
*
* @param align {string}
* @returns {jsPDF}
* @methodOf jsPDF#
* @name text
*/
API.text = function(text, x, y, options, transform) {
/**
* Inserts something like this into PDF
* BT
* /F1 16 Tf % Font name + size
* 16 TL % How many units down for next line in multiline text
* 0 g % color
* 28.35 813.54 Td % position
* (line one) Tj
* T* (line two) Tj
* T* (line three) Tj
* ET
*/
if (transform !== undefined && transform instanceof Matrix) {
advancedApiModeTrap("The transform parameter of text() with a Matrix value");
}
var xtra = "";
var isHex = false;
var lineHeight = lineHeightProportion;
var scope = this;
function ESC(s) {
s = s.split("\t").join(Array(options.TabLen || 9).join(" "));
return pdfEscape(s, flags);
}
function transformTextToSpecialArray(text) {
//we don't want to destroy original text array, so cloning it
var sa = text.concat();
var da = [];
var len = sa.length;
var curDa;
//we do array.join('text that must not be PDFescaped")
//thus, pdfEscape each component separately
while (len--) {
curDa = sa.shift();
if (typeof curDa === "string") {
da.push(curDa);
} else {
if (Object.prototype.toString.call(text) === "[object Array]" && curDa.length === 1) {
da.push(curDa[0]);
} else {
da.push([curDa[0], curDa[1], curDa[2]]);
}
}
}
return da;
}
function processTextByFunction(text, processingFunction) {
var result;
if (typeof text === "string") {
result = processingFunction(text)[0];
} else if (Object.prototype.toString.call(text) === "[object Array]") {
//we don't want to destroy original text array, so cloning it
var sa = text.concat();
var da = [];
var len = sa.length;
var curDa;
var tmpResult;
//we do array.join('text that must not be PDFescaped")
//thus, pdfEscape each component separately
while (len--) {
curDa = sa.shift();
if (typeof curDa === "string") {
da.push(processingFunction(curDa)[0]);
} else if (Object.prototype.toString.call(curDa) === "[object Array]" && curDa[0] === "string") {
tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]);
da.push([tmpResult[0], tmpResult[1], tmpResult[2]]);
}
}
result = da;
}
return result;
}
//backwardsCompatibility
var tmp;
// Pre-August-2012 the order of arguments was function(x, y, text, flags)
// in effort to make all calls have similar signature like
// function(data, coordinates... , miscellaneous)
// this method had its args flipped.
// code below allows backward compatibility with old arg order.
if (typeof text === "number") {
tmp = y;
y = x;
x = text;
text = tmp;
}
var flags = arguments[3];
var angle = arguments[4];
var align = arguments[5];
if (typeof flags !== "object" || flags === null) {
if (typeof angle === "string") {
align = angle;
angle = null;
}
if (typeof flags === "string") {
align = flags;
flags = null;
}
if (typeof flags === "number") {
angle = flags;
flags = null;
}
options = { flags: flags, angle: angle, align: align };
}
//Check if text is of type String
var textIsOfTypeString = false;
var tmpTextIsOfTypeString = true;
if (typeof text === "string") {
textIsOfTypeString = true;
} else if (Object.prototype.toString.call(text) === "[object Array]") {
//we don't want to destroy original text array, so cloning it
var sa = text.concat();
var da = [];
var len = sa.length;
var curDa;
//we do array.join('text that must not be PDFescaped")
//thus, pdfEscape each component separately
while (len--) {
curDa = sa.shift();
if (
typeof curDa !== "string" ||
(Object.prototype.toString.call(curDa) === "[object Array]" && typeof curDa[0] !== "string")
) {
tmpTextIsOfTypeString = false;
}
}
textIsOfTypeString = tmpTextIsOfTypeString;
}
if (textIsOfTypeString === false) {
throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.');
}
//Escaping
var activeFontEncoding = fonts[activeFontKey].encoding;
if (activeFontEncoding === "WinAnsiEncoding" || activeFontEncoding === "StandardEncoding") {
text = processTextByFunction(text, function(text, posX, posY) {
return [ESC(text), posX, posY];
});
}
//If there are any newlines in text, we assume
//the user wanted to print multiple lines, so break the
//text up into an array. If the text is already an array,
//we assume the user knows what they are doing.
//Convert text into an array anyway to simplify
//later code.
if (typeof text === "string") {
if (text.match(/[\r?\n]/)) {
text = text.split(/\r\n|\r|\n/g);
} else {
text = [text];
}
}
//multiline
var maxWidth = options.maxWidth || 0;
if (maxWidth > 0) {
if (typeof text === "string") {
text = scope.splitTextToSize(text, maxWidth);
} else if (Object.prototype.toString.call(text) === "[object Array]") {
text = scope.splitTextToSize(text.join(" "), maxWidth);
}
}
//creating Payload-Object to make text byRef
var payload = {
text: text,
x: x,
y: y,
options: options,
mutex: {
pdfEscape: pdfEscape,
activeFontKey: activeFontKey,
fonts: fonts,
activeFontSize: activeFontSize
}
};
events.publish("preProcessText", payload);
text = payload.text;
options = payload.options;
//angle
var angle = options.angle;
var curY = transformScaleY(y);
var transformationMatrix = null;
if (angle && typeof angle === "number") {
angle *= Math.PI / 180;
if (apiMode === ApiMode.ADVANCED) {
angle = -angle;
}
var c = Math.cos(angle),
s = Math.sin(angle);
transformationMatrix = new Matrix(c, s, -s, c, 0, 0);
} else if (angle && angle instanceof Matrix) {
transformationMatrix = angle;
}
//charSpace
var charSpace = options.charSpace;
if (charSpace !== undefined) {
xtra += charSpace + " Tc\n";
}
//lang
var lang = options.lang;
if (lang) {
// xtra += "/Lang (" + lang +")\n";
}
//renderingMode
var renderingMode = -1;
var tmpRenderingMode = -1;
var parmRenderingMode = options.renderingMode || options.stroke;
var pageContext = scope.internal.getCurrentPageInfo().pageContext;
switch (parmRenderingMode) {
case 0:
case false:
case "fill":
tmpRenderingMode = 0;
break;
case 1:
case true:
case "stroke":
tmpRenderingMode = 1;
break;
case 2:
case "fillThenStroke":
tmpRenderingMode = 2;
break;
case 3:
case "invisible":
tmpRenderingMode = 3;
break;
case 4:
case "fillAndAddForClipping":
tmpRenderingMode = 4;
break;
case 5:
case "strokeAndAddPathForClipping":
tmpRenderingMode = 5;
break;
case 6:
case "fillThenStrokeAndAddToPathForClipping":
tmpRenderingMode = 6;
break;
case 7:
case "addToPathForClipping":
tmpRenderingMode = 7;
break;
}
var usedRenderingMode = pageContext.usedRenderingMode || -1;
//if the coder wrote it explicitly to use a specific
//renderingMode, then use it
if (tmpRenderingMode !== -1) {
xtra += tmpRenderingMode + " Tr\n";
//otherwise check if we used the rendering Mode already
//if so then set the rendering Mode...
} else if (usedRenderingMode !== -1) {
xtra += "0 Tr\n";
}
if (tmpRenderingMode !== -1) {
pageContext.usedRenderingMode = tmpRenderingMode;
}
//align
var align = options.align || "left";
var leading = activeFontSize * lineHeight;
var pageWidth = scope.internal.pageSize.getWidth();
var k = scope.internal.scaleFactor;
var lineWidth = lineWidth;
var activeFont = fonts[activeFontKey];
var charSpace = options.charSpace || activeCharSpace;
var widths;
var maxWidth = options.maxWidth || 0;
var lineWidths;
var flags = {};
var wordSpacingPerLine = [];
if (Object.prototype.toString.call(text) === "[object Array]") {
var da = transformTextToSpecialArray(text);
var left = 0;
var newY;
var maxLineLength;
var lineWidths;
if (align !== "left") {
lineWidths = da.map(function(v) {
return (
(scope.getStringUnitWidth(v, {
font: activeFont,
charSpace: charSpace,
fontSize: activeFontSize
}) *
activeFontSize) /
k
);
});
}
var maxLineLength = Math.max.apply(Math, lineWidths);
//The first line uses the "main" Td setting,
//and the subsequent lines are offset by the
//previous line's x coordinate.
var prevWidth = 0;
var delta;
var newX;
var xOffset = 0;
if (align === "right") {
//The passed in x coordinate defines the
//rightmost point of the text.
left = x - maxLineLength;
xOffset = -lineWidths[0];
text = [];
for (var i = 0, len = da.length; i < len; i++) {
delta = maxLineLength - lineWidths[i];
if (i === 0) {
newX = 0;
newY = 0;
} else {
newX = prevWidth - lineWidths[i];
newY = leading;
}
text.push([da[i], newX, newY]);
prevWidth = lineWidths[i];
}
} else if (align === "center") {
//The passed in x coordinate defines
//the center point.
left = x - maxLineLength / 2;
xOffset = -lineWidths[0] / 2;
text = [];
for (var i = 0, len = da.length; i < len; i++) {
delta = (maxLineLength - lineWidths[i]) / 2;
if (i === 0) {
newX = 0;
newY = 0;
} else {
newX = (prevWidth - lineWidths[i]) / 2;
newY = leading;
}
text.push([da[i], newX, newY]);
prevWidth = lineWidths[i];
}
} else if (align === "left") {
text = [];
for (var i = 0, len = da.length; i < len; i++) {
text.push(da[i]);
}
} else if (align === "justify") {
text = [];
var maxWidth = maxWidth !== 0 ? maxWidth : pageWidth;
for (var i = 0, len = da.length; i < len; i++) {
newX = 0;
newY = i === 0 ? 0 : leading;
if (i < len - 1) {
wordSpacingPerLine.push((maxWidth - lineWidths[i]) / (da[i].split(" ").length - 1));
}
text.push([da[i], newX, newY]);
}
} else {
throw new Error('Unrecognized alignment option, use "left", "center", "right" or "justify".');
}
}
//R2L
var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L;
if (doReversing === true) {
text = processTextByFunction(text, function(text, posX, posY) {
return [
text
.split("")
.reverse()
.join(""),
posX,
posY
];
});
}
//creating Payload-Object to make text byRef
var payload = {
text: text,
x: x,
y: y,
options: options,
mutex: {
pdfEscape: pdfEscape,
activeFontKey: activeFontKey,
fonts: fonts,
activeFontSize: activeFontSize
}
};
events.publish("postProcessText", payload);
text = payload.text;
isHex = payload.mutex.isHex;
var da = transformTextToSpecialArray(text);
text = [];
var variant = 0;
var len = da.length;
var posX;
var posY;
var content;
var wordSpacing = "";
for (var i = 0; i < len; i++) {
wordSpacing = "";
if (Object.prototype.toString.call(da[i]) !== "[object Array]") {
content = (isHex ? "<" : "(") + da[i] + (isHex ? ">" : ")");
variant = 0;
} else if (Object.prototype.toString.call(da[i]) === "[object Array]") {
posX = da[i][1] * k; // x offset must always be scaled!
// y offset/leading must NOT be scaled by k as it is dependent of the font size, which is always given
// in plain pt
posY = -da[i][2];
content = (isHex ? "<" : "(") + da[i][0] + (isHex ? ">" : ")");
variant = 1;
}
if (wordSpacingPerLine !== undefined && wordSpacingPerLine[i] !== undefined) {
wordSpacing = wordSpacingPerLine[i] + " Tw\n";
}
if (variant === 1 && i > 0) {
text.push(wordSpacing + hpf(posX) + " " + hpf(posY) + " " + " Td\n" + content);
} else {
text.push(wordSpacing + content);
}
}
if (variant === 0) {
text = text.join(" Tj\nT* ");
} else {
text = text.join(" Tj\n");
}
if (apiMode === ApiMode.ADVANCED && transformationMatrix === null) {
transformationMatrix = unitMatrix;
}
if (transformationMatrix !== null) {
// It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y
// but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied
// in the coordinate system established by this matrix
if (typeof angle === "number") {
transformationMatrix = matrixMult(
transformationMatrix,
new Matrix(1, 0, 0, 1, scaleByK(x), transformScaleY(y))
);
} else {
transformationMatrix = matrixMult(
new Matrix(1, 0, 0, 1, scaleByK(x), transformScaleY(y)),
transformationMatrix
);
}
transformationMatrix = matrixMult(
// xOffset must always be scaled!
new Matrix(1, 0, 0, 1, xOffset * k, 0),
transformationMatrix
);
if (apiMode === ApiMode.ADVANCED) {
transformationMatrix = matrixMult(new Matrix(1, 0, 0, -1, 0, 0), transformationMatrix);
}
text = transformationMatrix.toString() + " Tm\n" + text;
} else {
text = hpf(scaleByK(x + xOffset)) + " " + hpf(transformScaleY(y)) + " " + " Td\n" + text;
}
text += " Tj\n";
var result =
"BT\n/" +
activeFontKey +
" " +
activeFontSize +
" Tf\n" + // font face, style, size
(activeFontSize * lineHeight).toFixed(2) +
" TL\n" + // line spacing
textColor +
"\n";
result += xtra;
result += text;
result += "ET";
out(result);
return scope;
};
/**
* Letter spacing method to print text with gaps
*
* @function
* @param {String|Array} text String to be added to the page.
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} spacing Spacing (in units declared at inception)
* @returns {jsPDF}
* @methodOf jsPDF#
* @name lstext
* @deprecated We'll be removing this function. It doesn't take character width into account.
*/
API.lstext = function(text, x, y, spacing) {
console.warn("jsPDF.lstext is deprecated");
for (var i = 0, len = text.length; i < len; i++, x += spacing) this.text(text[i], x, y);
return this;
};
/**
* Draws a line from (x1, y1) to (x2, y2). No extra call to {@link API.stroke} is needed.
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @return {jsPDF}
* @methodOf jsPDF#
* @name line
*/
API.line = function(x1, y1, x2, y2) {
if (apiMode === ApiMode.COMPAT) {
return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], "D");
} else {
return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke();
}
};
/**
* Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator.
* @param {number} x
* @param {number} y
* @methodOf jsPDF#
* @name moveTo
*/
API.moveTo = function(x, y) {
out(hpf(scaleByK(x)) + " " + hpf(transformScaleY(y)) + " m");
};
/**
* Append a straight line segment from the current point to the point (x, y). The PDF "l" operator.
* @param {number} x
* @param {number} y
* @methodOf jsPDF#
* @name lineTo
*/
API.lineTo = function(x, y) {
out(hpf(scaleByK(x)) + " " + hpf(transformScaleY(y)) + " l");
};
/**
* Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point
* (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3).
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @methodOf jsPDF#
* @name curveTo
*/
API.curveTo = function(x1, y1, x2, y2, x3, y3) {
out(
[
hpf(scaleByK(x1)),
hpf(transformScaleY(y1)),
hpf(scaleByK(x2)),
hpf(transformScaleY(y2)),
hpf(scaleByK(x3)),
hpf(transformScaleY(y3)),
"c"
].join(" ")
);
};
// PDF supports these path painting and clip path operators:
//
// S - stroke
// s - close/stroke
// f (F) - fill non-zero
// f* - fill evenodd
// B - fill stroke nonzero
// B* - fill stroke evenodd
// b - close fill stroke nonzero
// b* - close fill stroke evenodd
// n - nothing (consume path)
// W - clip nonzero
// W* - clip evenodd
//
// In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close()
// method.
/**
* Close the current path. The PDF "h" operator.
* @return jsPDF
* @methodOf jsPDF#
* @name close
*/
API.close = function() {
out("h");
return this;
};
/**
* Stroke the path. The PDF "S" operator.
* @return jsPDF
* @methodOf jsPDF#
* @name stroke
*/
API.stroke = function() {
out("S");
return this;
};
/**
* @typedef {Object} PatternData
* @property {string} key The key of the pattern
* @property {Matrix} matrix The matrix that gets applied to the pattern right before drawing.
* @property {number[]|undefined} boundingBox Only relevant for tiling patterns. The bounding box at which one
* pattern cell gets clipped
* @property {number|undefined} xStep Only relevant for tiling patterns. Horizontal spacing between pattern cells
* @property {number|undefined} yStep Only relevant for tiling patterns. Vertical spacing between pattern cells
*/
/**
* Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled
* with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator.
* @param {PatternData=} pattern If provided the path will be filled with this pattern
* @return jsPDF
* @methodOf jsPDF#
* @name fill
*/
API.fill = function(pattern) {
fillWithOptionalPattern("f", pattern);
return this;
};
/**
* Fill the current path using the even-odd rule. The PDF f* operator.
* @see API.fill
* @param {PatternData=} pattern Optional pattern
* @return jsPDF
* @methodOf jsPDF#
* @name fillEvenOdd
*/
API.fillEvenOdd = function(pattern) {
fillWithOptionalPattern("f*", pattern);
return this;
};
/**
* Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator.
* @see API.fill
* @param {PatternData=} pattern Optional pattern
* @return jsPDF
* @methodOf jsPDF#
* @name fillStroke
*/
API.fillStroke = function(pattern) {
fillWithOptionalPattern("B", pattern);
return this;
};
/**
* Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator.
* @see API.fill
* @param {PatternData=} pattern Optional pattern
* @return jsPDF
* @methodOf jsPDF#
* @name fillStrokeEvenOdd
*/
API.fillStrokeEvenOdd = function(pattern) {
fillWithOptionalPattern("B*", pattern);
return this;
};
function fillWithOptionalPattern(style, pattern) {
if (typeof pattern === "object") {
fillWithPattern(pattern, style);
} else {
out(style);
}
}
/**
* Modify the current clip path by intersecting it with the current path using the nonzero winding number rule. Note
* that this will NOT consume the current path. In order to only use this path for clipping call
* {@link API.discardPath} afterwards.
*
* When in "compat" API mode this method has a historical bug and will always stroke the path as well, use
* {@link API.clip_fixed} instead.
* @return jsPDF
* @methodOf jsPDF#
* @name clip
*/
API.clip = function() {
if (apiMode === ApiMode.COMPAT) {
// By patrick-roberts, github.com/MrRio/jsPDF/issues/328
// Call .clip() after calling .rect() with a style argument of null
out("W"); // clip
out("S"); // stroke path; necessary for clip to work
} else {
out("W");
}
return this;
};
/**
* Modify the current clip path by intersecting it with the current path using the even-odd rule. Note
* that this will NOT consume the current path. In order to only use this path for clipping call
* {@link API.discardPath} afterwards.
*
* @return jsPDF
* @methodOf jsPDF#
* @name clipEvenOdd
*/
API.clipEvenOdd = function() {
out("W*");
return this;
};
/**
* Consumes the current path without any effect. Mainly used in combination with {@link clip} or
* {@link clipEvenOdd}. The PDF "n" operator.
* @return {jsPDF}
* @methodOf jsPDF#
* @name discardPath
*/
API.discardPath = function() {
out("n");
return this;
};
/**
* This fixes the previous function clip(). Perhaps the 'stroke path' hack was due to the missing 'n' instruction?
* We introduce the fixed version so as to not break API.
* @param fillRule
* @deprecated Don't use this method when in "advanced" API mode.
* @methodOf jsPDF#
* @name clip_fixed
*/
API.clip_fixed = function(fillRule) {
// Call .clip() after calling drawing ops with a style argument of null
// W is the PDF clipping op
if ("evenodd" === fillRule) {
out("W*");
} else {
out("W");
}
// End the path object without filling or stroking it.
// This operator is a path-painting no-op, used primarily for the side effect of changing the current clipping path
// (see Section 4.4.3, “Clipping Path Operators”)
out("n");
};
/**
* @typedef {Object} PatternData
* {Matrix|undefined} matrix
* {Number|undefined} xStep
* {Number|undefined} yStep
* {Array.<Number>|undefined} boundingBox
*/
/**
* Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates.
* All data points in `lines` are relative to last line origin.
* `x`, `y` become x1,y1 for first line / curve in the set.
* For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point.
* For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1.
*
* @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line
* @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves).
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction.
* @param {String=} style A string specifying the painting style or null. Valid styles include:
* 'S' [default] - stroke,
* 'F' - fill,
* and 'DF' (or 'FD') - fill then stroke.
* In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
* method calls. The last drawing method call used to define the shape should not have a null style argument.
*
* In "advanced" API mode this parameter is deprecated.
* @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the path. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name lines
*/
API.lines = function(lines, x, y, scale, style, closed, patternKey, patternData) {
var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4;
// Pre-August-2012 the order of arguments was function(x, y, lines, scale, style)
// in effort to make all calls have similar signature like
// function(content, coordinateX, coordinateY , miscellaneous)
// this method had its args flipped.
// code below allows backward compatibility with old arg order.
if (typeof lines === "number") {
var tmp = y;
y = x;
x = lines;
lines = tmp;
}
scale = scale || [1, 1];
// starting point
this.moveTo(x, y);
scalex = scale[0];
scaley = scale[1];
l = lines.length;
//, x2, y2 // bezier only. In page default measurement "units", *after* scaling
//, x3, y3 // bezier only. In page default measurement "units", *after* scaling
// ending point for all, lines and bezier. . In page default measurement "units", *after* scaling
x4 = x; // last / ending point = starting point for first item.
y4 = y; // last / ending point = starting point for first item.
for (i = 0; i < l; i++) {
leg = lines[i];
if (leg.length === 2) {
// simple line
x4 = leg[0] * scalex + x4; // here last x4 was prior ending point
y4 = leg[1] * scaley + y4; // here last y4 was prior ending point
this.lineTo(x4, y4);
} else {
// bezier curve
x2 = leg[0] * scalex + x4; // here last x4 is prior ending point
y2 = leg[1] * scaley + y4; // here last y4 is prior ending point
x3 = leg[2] * scalex + x4; // here last x4 is prior ending point
y3 = leg[3] * scaley + y4; // here last y4 is prior ending point
x4 = leg[4] * scalex + x4; // here last x4 was prior ending point
y4 = leg[5] * scaley + y4; // here last y4 was prior ending point
this.curveTo(x2, y2, x3, y3, x4, y4);
}
}
if (closed) {
this.close();
}
putStyle(style, patternKey, patternData);
return this;
};
/**
* Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative.
* @param {Array<Object>} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to)
* "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c"
* six and "h" an empty array (or undefined).
* @param {String=} style The style. Deprecated!
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the path. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name path
*/
API.path = function(lines, style, patternKey, patternData) {
for (var i = 0; i < lines.length; i++) {
var leg = lines[i];
var coords = leg.c;
switch (leg.op) {
case "m":
this.moveTo(coords[0], coords[1]);
break;
case "l":
this.lineTo(coords[0], coords[1]);
break;
case "c":
this.curveTo.apply(this, coords);
break;
case "h":
this.close();
break;
}
}
putStyle(style, patternKey, patternData);
return this;
};
/**
* Adds a rectangle to PDF
*
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} w Width (in units declared at inception of PDF document)
* @param {Number} h Height (in units declared at inception of PDF document)
* @param {String=} style A string specifying the painting style or null. Valid styles include:
* 'S' [default] - stroke,
* 'F' - fill,
* and 'DF' (or 'FD') - fill then stroke.
* In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
* method calls. The last drawing method call used to define the shape should not have a null style argument.
*
* In "advanced" API mode this parameter is deprecated.
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name rect
*/
API.rect = function(x, y, w, h, style, patternKey, patternData) {
if (apiMode === ApiMode.COMPAT) {
h = -h;
}
out([hpf(scaleByK(x)), hpf(transformScaleY(y)), hpf(scaleByK(w)), hpf(scaleByK(h)), "re"].join(" "));
putStyle(style, patternKey, patternData);
return this;
};
/**
* Adds a triangle to PDF
*
* @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {String=} style A string specifying the painting style or null. Valid styles include:
* 'S' [default] - stroke,
* 'F' - fill,
* and 'DF' (or 'FD') - fill then stroke.
* In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
* method calls. The last drawing method call used to define the shape should not have a null style argument.
*
* In "advanced" API mode this parameter is deprecated.
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name triangle
*/
API.triangle = function(x1, y1, x2, y2, x3, y3, style, patternKey, patternData) {
this.lines(
[
[x2 - x1, y2 - y1], // vector to point 2
[x3 - x2, y3 - y2], // vector to point 3
[x1 - x3, y1 - y3] // closing vector back to point 1
],
x1,
y1, // start of path
[1, 1],
style,
true,
patternKey,
patternData
);
return this;
};
/**
* Adds a rectangle with rounded corners to PDF
*
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} w Width (in units declared at inception of PDF document)
* @param {Number} h Height (in units declared at inception of PDF document)
* @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
* @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
* @param {String=} style A string specifying the painting style or null. Valid styles include:
* 'S' [default] - stroke,
* 'F' - fill,
* and 'DF' (or 'FD') - fill then stroke.
* In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
* method calls. The last drawing method call used to define the shape should not have a null style argument.
*
* In "advanced" API mode this parameter is deprecated.
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name roundedRect
*/
API.roundedRect = function(x, y, w, h, rx, ry, style, patternKey, patternData) {
var MyArc = (4 / 3) * (Math.SQRT2 - 1);
rx = Math.min(rx, w * 0.5);
ry = Math.min(ry, h * 0.5);
this.lines(
[
[w - 2 * rx, 0],
[rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry],
[0, h - 2 * ry],
[0, ry * MyArc, -(rx * MyArc), ry, -rx, ry],
[-w + 2 * rx, 0],
[-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry],
[0, -h + 2 * ry],
[0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]
],
x + rx,
y, // start of path
[1, 1],
style,
true,
patternKey,
patternData
);
return this;
};
/**
* Adds an ellipse to PDF
*
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
* @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
* @param {String=} style A string specifying the painting style or null. Valid styles include:
* 'S' [default] - stroke,
* 'F' - fill,
* and 'DF' (or 'FD') - fill then stroke.
* In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
* method calls. The last drawing method call used to define the shape should not have a null style argument.
*
* In "advanced" API mode this parameter is deprecated.
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name ellipse
*/
API.ellipse = function(x, y, rx, ry, style, patternKey, patternData) {
var lx = (4 / 3) * (Math.SQRT2 - 1) * rx,
ly = (4 / 3) * (Math.SQRT2 - 1) * ry;
this.moveTo(x + rx, y);
this.curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry);
this.curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y);
this.curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry);
this.curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y);
putStyle(style, patternKey, patternData);
return this;
};
/**
* Adds an circle to PDF
*
* @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
* @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
* @param {Number} r Radius (in units declared at inception of PDF document)
* @param {String=} style A string specifying the painting style or null. Valid styles include:
* 'S' [default] - stroke,
* 'F' - fill,
* and 'DF' (or 'FD') - fill then stroke.
* In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
* method calls. The last drawing method call used to define the shape should not have a null style argument.
*
* In "advanced" API mode this parameter is deprecated.
* @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
* @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
* will modify the pattern on use. Deprecated!
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name circle
*/
API.circle = function(x, y, r, style, patternKey, patternData) {
return this.ellipse(x, y, r, r, style, patternKey, patternData);
};
/**
* Adds a properties to the PDF document
*
* @param {Object} properties A property_name-to-property_value object structure.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setProperties
*/
API.setProperties = function(properties) {
// copying only those properties we can render.
for (var property in documentProperties) {
if (documentProperties.hasOwnProperty(property) && properties[property]) {
documentProperties[property] = properties[property];
}
}
return this;
};
/**
* Sets font size for upcoming text elements.
*
* @param {Number} size Font size in points.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setFontSize
*/
API.setFontSize = function(size) {
// convert font size into current unit system
if (apiMode === ApiMode.ADVANCED) {
activeFontSize = size / k;
} else {
activeFontSize = size;
}
out("/" + activeFontKey + " " + activeFontSize + " Tf");
return this;
};
/**
* @return {number}
* @methodOf jsPDF#
* @name getFontSize
*/
API.getFontSize = function() {
if (apiMode === ApiMode.COMPAT) {
return activeFontSize;
} else {
return activeFontSize * k;
}
};
/**
* Sets text font face, variant for upcoming text elements.
* See output of jsPDF.getFontList() for possible font names, styles.
*
* @param {String} fontName Font name or family. Example: "times"
* @param {String} fontStyle Font style or variant. Example: "italic"
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setFont
*/
API.setFont = function(fontName, fontStyle) {
activeFontKey = getFont(fontName, fontStyle);
// if font is not found, the above line blows up and we never go further
out("/" + activeFontKey + " " + activeFontSize + " Tf");
return this;
};
/**
* Switches font style or variant for upcoming text elements,
* while keeping the font face or family same.
* See output of jsPDF.getFontList() for possible font names, styles.
*
* @param {String} style Font style or variant. Example: "italic"
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setFontStyle
*/
API.setFontStyle = API.setFontType = function(style) {
activeFontKey = getFont(undefined, style);
// if font is not found, the above line blows up and we never go further
out("/" + activeFontKey + " " + activeFontSize + " Tf");
return this;
};
/**
* Returns an object - a tree of fontName to fontStyle relationships available to
* active PDF document.
*
* @public
* @function
* @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... }
* @methodOf jsPDF#
* @name getFontList
*/
API.getFontList = function() {
// TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added.
var list = {},
fontName,
fontStyle,
tmp;
for (fontName in fontmap) {
if (fontmap.hasOwnProperty(fontName)) {
list[fontName] = tmp = [];
for (fontStyle in fontmap[fontName]) {
if (fontmap[fontName].hasOwnProperty(fontStyle)) {
tmp.push(fontStyle);
}
}
}
}
return list;
};
/**
* Add a custom font.
*
* @param {String} postScriptName name of the Font. Example: "Menlo-Regular"
* @param {String} fontName of font-family from @font-face definition. Example: "Menlo Regular"
* @param {String} fontStyle style. Example: "normal"
* @function
* @returns the {fontKey} (same as the internal method)
* @methodOf jsPDF#
* @name addFont
*/
API.addFont = function(postScriptName, fontName, fontStyle, encoding) {
encoding = encoding || "Identity-H";
addFont(postScriptName, fontName, fontStyle, encoding);
};
/**
* Sets line width for upcoming lines.
*
* @param {Number} width Line width (in units declared at inception of PDF document)
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setLineWidth
*/
API.setLineWidth = function(width) {
out(scaleByK(width).toFixed(2) + " w");
return this;
};
/**
* Sets the stroke color for upcoming elements.
*
* Depending on the number of arguments given, Gray, RGB, or CMYK
* color space is implied.
*
* When only ch1 is given, "Gray" color space is implied and it
* must be a value in the range from 0.00 (solid black) to to 1.00 (white)
* if values are communicated as String types, or in range from 0 (black)
* to 255 (white) if communicated as Number type.
* The RGB-like 0-255 range is provided for backward compatibility.
*
* When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
* value must be in the range from 0.00 (minimum intensity) to to 1.00
* (max intensity) if values are communicated as String types, or
* from 0 (min intensity) to to 255 (max intensity) if values are communicated
* as Number types.
* The RGB-like 0-255 range is provided for backward compatibility.
*
* When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
* value must be a in the range from 0.00 (0% concentration) to to
* 1.00 (100% concentration)
*
* Because JavaScript treats fixed point numbers badly (rounds to
* floating point nearest to binary representation) it is highly advised to
* communicate the fractional numbers as String types, not JavaScript Number type.
*
* @param {Number|String} ch1 Color channel value or {String} ch1 color value in hexadecimal, example: '#FFFFFF'
* @param {Number|String} ch2 Color channel value
* @param {Number|String} ch3 Color channel value
* @param {Number|String} ch4 Color channel value
*
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setDrawColor
*/
API.setDrawColor = function(ch1, ch2, ch3, ch4) {
var options = {
ch1: ch1,
ch2: ch2,
ch3: ch3,
ch4: ch4,
pdfColorType: "draw",
precision: 2
};
out(generateColorString(options));
return this;
};
/**
* Sets the fill color for upcoming elements.
*
* Depending on the number of arguments given, Gray, RGB, or CMYK
* color space is implied.
*
* When only ch1 is given, "Gray" color space is implied and it
* must be a value in the range from 0.00 (solid black) to to 1.00 (white)
* if values are communicated as String types, or in range from 0 (black)
* to 255 (white) if communicated as Number type.
* The RGB-like 0-255 range is provided for backward compatibility.
*
* When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
* value must be in the range from 0.00 (minimum intensity) to to 1.00
* (max intensity) if values are communicated as String types, or
* from 0 (min intensity) to to 255 (max intensity) if values are communicated
* as Number types.
* The RGB-like 0-255 range is provided for backward compatibility.
*
* When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
* value must be a in the range from 0.00 (0% concentration) to to
* 1.00 (100% concentration)
*
* Because JavaScript treats fixed point numbers badly (rounds to
* floating point nearest to binary representation) it is highly advised to
* communicate the fractional numbers as String types, not JavaScript Number type.
*
* @param {Number|String} ch1 Color channel value or {String} ch1 color value in hexadecimal, example: '#FFFFFF'
* @param {Number|String} ch2 Color channel value
* @param {Number|String} ch3 Color channel value
* @param {Number|String} ch4 Color channel value
*
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setFillColor
*/
API.setFillColor = function(ch1, ch2, ch3, ch4) {
var options = {
ch1: ch1,
ch2: ch2,
ch3: ch3,
ch4: ch4,
pdfColorType: "fill",
precision: 2
};
out(generateColorString(options));
return this;
};
/**
* Sets the text color for upcoming elements.
*
* Depending on the number of arguments given, Gray, RGB, or CMYK
* color space is implied.
*
* When only ch1 is given, "Gray" color space is implied and it
* must be a value in the range from 0.00 (solid black) to to 1.00 (white)
* if values are communicated as String types, or in range from 0 (black)
* to 255 (white) if communicated as Number type.
* The RGB-like 0-255 range is provided for backward compatibility.
*
* When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
* value must be in the range from 0.00 (minimum intensity) to to 1.00
* (max intensity) if values are communicated as String types, or
* from 0 (min intensity) to to 255 (max intensity) if values are communicated
* as Number types.
* The RGB-like 0-255 range is provided for backward compatibility.
*
* When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
* value must be a in the range from 0.00 (0% concentration) to to
* 1.00 (100% concentration)
*
* Because JavaScript treats fixed point numbers badly (rounds to
* floating point nearest to binary representation) it is highly advised to
* communicate the fractional numbers as String types, not JavaScript Number type.
*
* @param {Number|String} ch1 Color channel value or {String} ch1 color value in hexadecimal, example: '#FFFFFF'
* @param {Number|String} ch2 Color channel value
* @param {Number|String} ch3 Color channel value
* @param {Number|String} ch4 Color channel value
*
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setTextColor
*/
API.setTextColor = function(ch1, ch2, ch3, ch4) {
var options = {
ch1: ch1,
ch2: ch2,
ch3: ch3,
ch4: ch4,
pdfColorType: "text",
precision: 3
};
textColor = generateColorString(options);
return this;
};
/**
* Initializes the default character set that the user wants to be global..
*
* @param {Number} charSpace
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setCharSpace
*/
API.setCharSpace = function(charSpace) {
if (apiMode === ApiMode.COMPAT) {
activeCharSpace = charSpace;
} else if (apiMode === ApiMode.ADVANCED) {
activeCharSpace = charSpace / k;
}
return this;
};
/**
* Initializes the default character set that the user wants to be global..
*
* @param {Boolean} boolean
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setR2L
*/
API.setR2L = function(boolean) {
R2L = boolean;
return this;
};
/**
* Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}.
* @param {String|GState} gState If type is string, a previously added GState is used, if type is GState
* it will be added before use.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setGState
*/
API.setGState = function(gState) {
if (typeof gState === "string") {
gState = gStates[gStatesMap[gState]];
} else {
gState = addGState(null, gState);
}
if (!gState.equals(activeGState)) {
out("/" + gState.id + " gs");
activeGState = gState;
}
};
/**
* Is an Object providing a mapping from human-readable to
* integer flag values designating the varieties of line cap
* and join styles.
*
* @returns {Object}
* @fieldOf jsPDF#
* @name CapJoinStyles
*/
API.CapJoinStyles = {
0: 0,
butt: 0,
but: 0,
miter: 0,
1: 1,
round: 1,
rounded: 1,
circle: 1,
2: 2,
projecting: 2,
project: 2,
square: 2,
bevel: 2
};
/**
* Sets the line cap styles
* See {jsPDF.CapJoinStyles} for variants
*
* @param {String|Number} style A string or number identifying the type of line cap
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setLineCap
*/
API.setLineCap = function(style) {
var id = this.CapJoinStyles[style];
if (id === undefined) {
throw new Error(
"Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"
);
}
lineCapID = id;
out(id + " J");
return this;
};
/**
* Sets the line join styles
* See {jsPDF.CapJoinStyles} for variants
*
* @param {String|Number} style A string or number identifying the type of line join
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setLineJoin
*/
API.setLineJoin = function(style) {
var id = this.CapJoinStyles[style];
if (id === undefined) {
throw new Error(
"Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"
);
}
lineJoinID = id;
out(id + " j");
return this;
};
/**
* Sets the miter limit.
* @param {number} miterLimit
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setMiterLimit
*/
API.setLineMiterLimit = function(miterLimit) {
out(hpf(miterLimit) + " M");
return this;
};
/**
* Sets the line dash pattern.
* @param {Array<number>} array An array containing 0-2 numbers. The first number sets the length of the
* dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered
* to be as long as the dashes. An empty array means solid, unbroken lines.
* @param phase The phase lines start with.
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name setLineDashPattern
*/
API.setLineDashPattern = function(array, phase) {
out(
[
"[" + (array[0] !== undefined ? array[0] : ""),
(array[1] !== undefined ? array[1] : "") + "]",
phase,
"d"
].join(" ")
);
return this;
};
// Output is both an internal (for plugins) and external function
API.output = output;
/**
* Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf')
* @param {String} filename The filename including extension.
*
* @function
* @returns {jsPDF}
* @methodOf jsPDF#
* @name save
*/
API.save = function(filename) {
API.output("save", filename);
};
// applying plugins (more methods) ON TOP of built-in API.
// this is intentional as we allow plugins to override
// built-ins
for (var plugin in jsPDF.API) {
if (jsPDF.API.hasOwnProperty(plugin)) {
if (plugin === "events" && jsPDF.API.events.length) {
(function(events, newEvents) {
// jsPDF.API.events is a JS Array of Arrays
// where each Array is a pair of event name, handler
// Events were added by plugins to the jsPDF instantiator.
// These are always added to the new instance and some ran
// during instantiation.
var eventname, handler_and_args, i;
for (i = newEvents.length - 1; i !== -1; i--) {
// subscribe takes 3 args: 'topic', function, runonce_flag
// if undefined, runonce is false.
// users can attach callback directly,
// or they can attach an array with [callback, runonce_flag]
// that's what the "apply" magic is for below.
eventname = newEvents[i][0];
handler_and_args = newEvents[i][1];
events.subscribe.apply(
events,
[eventname].concat(typeof handler_and_args === "function" ? [handler_and_args] : handler_and_args)
);
}
})(events, jsPDF.API.events);
} else {
API[plugin] = jsPDF.API[plugin];
}
}
}
//////////////////////////////////////////////////////
// continuing initialization of jsPDF Document object
//////////////////////////////////////////////////////
// Add the first page automatically
addFonts();
activeFontKey = "F1";
_addPage(format, orientation);
events.publish("initialized");
return API;
}
/**
* jsPDF.API is a STATIC property of jsPDF class.
* jsPDF.API is an object you can add methods and properties to.
* The methods / properties you add will show up in new jsPDF objects.
*
* One property is prepopulated. It is the 'events' Object. Plugin authors can add topics,
* callbacks to this object. These will be reassigned to all new instances of jsPDF.
* Examples:
* jsPDF.API.events['initialized'] = function(){ 'this' is API object }
* jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object }
*
* @static
* @public
* @memberOf jsPDF
* @name API
*
* @example
* jsPDF.API.mymethod = function(){
* // 'this' will be ref to internal API object. see jsPDF source
* // , so you can refer to built-in methods like so:
* // this.line(....)
* // this.text(....)
* }
* var pdfdoc = new jsPDF()
* pdfdoc.mymethod() // <- !!!!!!
*/
jsPDF.API = {
events: []
};
jsPDF.version = "${versionID}" === "${vers" + "ionID}" ? "0.0.0" : "${versionID}";
if (typeof define === "function" && define.amd) {
define("jsPDF", function() {
return jsPDF;
});
} else if (typeof module !== "undefined" && module.exports) {
module.exports = jsPDF;
module.exports.jsPDF = jsPDF;
} else {
global.jsPDF = jsPDF;
}
return jsPDF;
})(
(typeof self !== "undefined" && self) ||
(typeof window !== "undefined" && window) ||
(typeof global !== "undefined" && global) ||
Function('return typeof this === "object" && this.content')() ||
Function("return this")()
);
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window