/** @preserve |
|
* jsPDF - PDF Document creation from JavaScript |
|
* Version ${versionID} |
|
* CommitID ${commitID} |
|
* |
|
* Copyright (c) 2010-2014 James Hall , 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. |
|
* |
|
* @class |
|
* @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l") |
|
* @param unit Measurement unit to be used when coordinates are specified. |
|
* One of "pt" (points), "mm" (Default), "cm", "in" |
|
* @param format One of 'pageFormats' as shown below, default: a4 |
|
* @returns {jsPDF} |
|
* @name jsPDF |
|
*/ |
|
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.40, 209.76], 'a9' : [ 104.88, 147.40], |
|
'a10' : [ 73.70, 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.90, 708.66], 'b6' : [ 354.33, 498.90], |
|
'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 |
|
*/ |
|
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, |
|
lineHeightProportion = options.lineHeight || 1.15, |
|
lineWidth = options.lineWidth || 0.200025, // 2mm |
|
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 |
|
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, |
|
pageWidth, |
|
pageHeight, |
|
pageMode, |
|
zoomMode, |
|
layoutMode, |
|
documentProperties = { |
|
'title' : '', |
|
'subject' : '', |
|
'author' : '', |
|
'keywords' : '', |
|
'creator' : '' |
|
}, |
|
API = {}, |
|
events = new PubSub(API), |
|
|
|
/ |
|
// Private functions |
|
/ |
|
f2 = function(number) { |
|
return number.toFixed(2); // Ie, %.2f |
|
}, |
|
f3 = function(number) { |
|
return number.toFixed(3); // Ie, %.3f |
|
}, |
|
padd2 = function(number) { |
|
return ('0' + parseInt(number)).slice(-2); |
|
}, |
|
out = function(string) { |
|
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; |
|
|
|
adler32cs = global.adler32cs || jsPDF.adler32cs; |
|
if (compress && typeof adler32cs === 'undefined') { |
|
compress = false; |
|
} |
|
|
|
// outToPages = false as set in endDocument(). out() writes to content. |
|
|
|
for (n = 1; n <= page; n++) { |
|
newObject(); |
|
wPt = (pageWidth = pagedim[n].width) * k; |
|
hPt = (pageHeight = pagedim[n].height) * k; |
|
out('<'); |
|
out('/Parent 1 0 R'); |
|
out('/Resources 2 0 R'); |
|
out('/MediaBox [0 0 ' + f2(wPt) + ' ' + f2(hPt) + ']'); |
|
out('/Contents ' + (objectNumber + 1) + ' 0 R'); |
|
// Added for annotation plugin |
|
events.publish('putPage', {pageNumber:n,page:pages[n]}); |
|
out('>>'); |
|
out('endobj'); |
|
|
|
// Page content |
|
p = pages[n].join('\n'); |
|
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('<' + p.length + ' /Filter [/FlateDecode]>>'); |
|
} else { |
|
out('<' + p.length + '>>'); |
|
} |
|
putStream(p); |
|
out('endobj'); |
|
} |
|
offsets[1] = content_length; |
|
out('1 0 obj'); |
|
out('<'); |
|
var kids = '/Kids ['; |
|
for (i = 0; i < page; i++) { |
|
kids += (3 + 2 * i) + ' 0 R '; |
|
} |
|
out(kids + ']'); |
|
out('/Count ' + page); |
|
out('>>'); |
|
out('endobj'); |
|
}, |
|
putFont = function(font) { |
|
font.objectNumber = newObject(); |
|
out('<' + font.PostScriptName + '/Type/Font'); |
|
if (typeof font.encoding === 'string') { |
|
out('/Encoding/' + font.encoding); |
|
} |
|
out('/Subtype/Type1>>'); |
|
out('endobj'); |
|
}, |
|
putFonts = function() { |
|
for (var fontKey in fonts) { |
|
if (fonts.hasOwnProperty(fontKey)) { |
|
putFont(fonts[fontKey]); |
|
} |
|
} |
|
}, |
|
putXobjectDict = function() { |
|
// Loop through images, or other data objects |
|
events.publish('putXobjectDict'); |
|
}, |
|
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('/XObject <<'); |
|
putXobjectDict(); |
|
out('>>'); |
|
}, |
|
putResources = function() { |
|
putFonts(); |
|
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 |
|
*/ |
|
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', |
|
standardFonts = [ |
|
['Helvetica', HELVETICA, NORMAL], |
|
['Helvetica-Bold', HELVETICA, BOLD], |
|
['Helvetica-Oblique', HELVETICA, ITALIC], |
|
['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC], |
|
['Courier', COURIER, NORMAL], |
|
['Courier-Bold', COURIER, BOLD], |
|
['Courier-Oblique', COURIER, ITALIC], |
|
['Courier-BoldOblique', COURIER, BOLD_ITALIC], |
|
['Times-Roman', TIMES, NORMAL], |
|
['Times-Bold', TIMES, BOLD], |
|
['Times-Italic', TIMES, ITALIC], |
|
['Times-BoldItalic', TIMES, BOLD_ITALIC] |
|
]; |
|
|
|
for (var i = 0, l = standardFonts.length; i < l; i++) { |
|
var fontKey = addFont( |
|
standardFonts[i][0], |
|
standardFonts[i][1], |
|
standardFonts[i][2], |
|
encoding); |
|
|
|
// 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 }); |
|
}, |
|
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]) + ')'); |
|
} |
|
} |
|
var created = new Date(), |
|
tzoffset = created.getTimezoneOffset(), |
|
tzsign = tzoffset < 0 ? '+' : '-', |
|
tzhour = Math.floor(Math.abs(tzoffset / 60)), |
|
tzmin = Math.abs(tzoffset % 60), |
|
tzstr = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(''); |
|
out(['/CreationDate (D:', |
|
created.getFullYear(), |
|
padd2(created.getMonth() + 1), |
|
padd2(created.getDate()), |
|
padd2(created.getHours()), |
|
padd2(created.getMinutes()), |
|
padd2(created.getSeconds()), tzstr, ')'].join('')); |
|
}, |
|
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'); |
|
}, |
|
beginPage = 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; } |
|
} |
|
outToPages = true; |
|
pages[++page] = []; |
|
pagedim[page] = { |
|
width : Number(width) || pageWidth, |
|
height : Number(height) || pageHeight |
|
}; |
|
pagesContext[page] = {}; |
|
_setPage(page); |
|
}, |
|
_addPage = function() { |
|
beginPage.apply(this, arguments); |
|
// Set line width |
|
out(f2(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 fontName {String} can be undefined on "falthy" to indicate "use current" |
|
* @param fontStyle {String} can be undefined on "falthy" to indicate "use current" |
|
* @returns {String} Font key. |
|
*/ |
|
getFont = function(fontName, fontStyle) { |
|
var key; |
|
|
|
fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName; |
|
fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; |
|
|
|
if (fontName !== undefined){ |
|
fontName = fontName.toLowerCase(); |
|
} |
|
switch(fontName){ |
|
case 'sans-serif': |
|
case 'verdana': |
|
case 'arial': |
|
fontName = 'helvetica'; |
|
break; |
|
case 'fixed': |
|
case 'monospace': |
|
case 'terminal': |
|
fontName = 'courier'; |
|
break; |
|
case 'serif': |
|
case 'cursive': |
|
case 'fantasy': |
|
default: |
|
fontName = 'times'; |
|
break; |
|
} |
|
|
|
try { |
|
// get a string like 'F3' - the KEY corresponding tot he font + type combination. |
|
key = fontmap[fontName][fontStyle]; |
|
} catch (e) {} |
|
|
|
if (!key) { |
|
//throw new Error("Unable to look up font label for font '" + fontName + "', '" |
|
//+ fontStyle + "'. Refer to getFontList() for available fonts."); |
|
key = fontmap['times'][fontStyle]; |
|
if (key == null){ |
|
key = fontmap['times']['normal']; |
|
} |
|
} |
|
return key; |
|
}, |
|
buildDocument = function() { |
|
|
|
outToPages = false; // switches out() to content |
|
objectNumber = 2; |
|
content = []; |
|
offsets = []; |
|
additionalObjects = []; |
|
|
|
// putHeader() |
|
out('%PDF-' + pdfVersion); |
|
|
|
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 |
|
var op = 'S'; // stroke |
|
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; |
|
}, |
|
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 (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 |
|
}); |
|
|
|
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': k = 96 / 72; break; |
|
case 'pc': k = 12; break; |
|
case 'em': k = 12; break; |
|
case 'ex': k = 6; break; |
|
default: |
|
throw ('Invalid unit: ' + unit); |
|
} |
|
|
|
//--------------------------------------- |
|
// Public API |
|
|
|
/** |
|
* Object exposing internal API to plugins |
|
* @public |
|
*/ |
|
API.internal = { |
|
'pdfEscape' : pdfEscape, |
|
'getStyle' : getStyle, |
|
/** |
|
* Returns {FontObject} describing a particular font. |
|
* @public |
|
* @function |
|
* @param fontName {String} (Optional) Font's family name |
|
* @param fontStyle {String} (Optional) Font's style variation name (Example:"Italic") |
|
* @returns {FontObject} |
|
*/ |
|
'getFont' : function() { |
|
return fonts[getFont.apply(API, arguments)]; |
|
}, |
|
'getFontSize' : function() { |
|
return activeFontSize; |
|
}, |
|
'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 f2(value * k); |
|
}, |
|
'getVerticalCoordinateString' : function(value) { |
|
return f2((pageHeight - value) * k); |
|
}, |
|
'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' : { |
|
get width() { |
|
return pageWidth |
|
}, |
|
get height() { |
|
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]}; |
|
} |
|
}; |
|
|
|
/** |
|
* Adds (and transfers the focus to) new page to the PDF document. |
|
* @function |
|
* @returns {jsPDF} |
|
* |
|
* @methodOf jsPDF# |
|
* @name addPage |
|
*/ |
|
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) { |
|
if (targetPage > beforePage){ |
|
var tmpPages = pages[targetPage]; |
|
var tmpPagedim = pagedim[targetPage]; |
|
var tmpPagesContext = pagesContext[targetPage]; |
|
for (var 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){ |
|
var tmpPages = pages[targetPage]; |
|
var tmpPagedim = pagedim[targetPage]; |
|
var tmpPagesContext = pagesContext[targetPage]; |
|
for (var 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.setDisplayMode = function(zoom, layout, pmode) { |
|
zoomMode = zoom; |
|
layoutMode = layout; |
|
pageMode = pmode; |
|
return this; |
|
}, |
|
|
|
/** |
|
* 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} flags 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. |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name text |
|
*/ |
|
API.text = function(text, x, y, flags, angle, align) { |
|
/** |
|
* 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 |
|
*/ |
|
function ESC(s) { |
|
s = s.split("\t").join(Array(options.TabLen||9).join(" ")); |
|
return pdfEscape(s, flags); |
|
} |
|
|
|
// 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; |
|
} |
|
|
|
// 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(/[\n\r]/)) { |
|
text = text.split( /\r\n|\r|\n/g); |
|
} else { |
|
text = [text]; |
|
} |
|
} |
|
if (typeof angle === 'string') { |
|
align = angle; |
|
angle = null; |
|
} |
|
if (typeof flags === 'string') { |
|
align = flags; |
|
flags = null; |
|
} |
|
if (typeof flags === 'number') { |
|
angle = flags; |
|
flags = null; |
|
} |
|
var xtra = '',mode = 'Td', todo; |
|
if (angle) { |
|
angle *= (Math.PI / 180); |
|
var c = Math.cos(angle), |
|
s = Math.sin(angle); |
|
xtra = [f2(c), f2(s), f2(s * -1), f2(c), ''].join(" "); |
|
mode = 'Tm'; |
|
} |
|
flags = flags || {}; |
|
if (!('noBOM' in flags)) |
|
flags.noBOM = true; |
|
if (!('autoencode' in flags)) |
|
flags.autoencode = true; |
|
|
|
var strokeOption = ''; |
|
var pageContext = this.internal.getCurrentPageInfo().pageContext; |
|
if (true === flags.stroke){ |
|
if (pageContext.lastTextWasStroke !== true){ |
|
strokeOption = '1 Tr\n'; |
|
pageContext.lastTextWasStroke = true; |
|
} |
|
} |
|
else{ |
|
if (pageContext.lastTextWasStroke){ |
|
strokeOption = '0 Tr\n'; |
|
} |
|
pageContext.lastTextWasStroke = false; |
|
} |
|
|
|
if (typeof this._runningPageHeight === 'undefined'){ |
|
this._runningPageHeight = 0; |
|
} |
|
|
|
if (typeof text === 'string') { |
|
text = ESC(text); |
|
} else if (text instanceof Array) { |
|
// we don't want to destroy original text array, so cloning it |
|
var sa = text.concat(), da = [], len = sa.length; |
|
// we do array.join('text that must not be PDFescaped") |
|
// thus, pdfEscape each component separately |
|
while (len--) { |
|
da.push(ESC(sa.shift())); |
|
} |
|
var linesLeft = Math.ceil((pageHeight - y - this._runningPageHeight) * k / (activeFontSize * lineHeightProportion)); |
|
if (0 <= linesLeft && linesLeft < da.length + 1) { |
|
//todo = da.splice(linesLeft-1); |
|
} |
|
|
|
if( align ) { |
|
var left, |
|
prevX, |
|
maxLineLength, |
|
leading = activeFontSize * lineHeightProportion, |
|
lineWidths = text.map( function( v ) { |
|
return this.getStringUnitWidth( v ) * activeFontSize / k; |
|
}, this ); |
|
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. |
|
if( align === "center" ) { |
|
// The passed in x coordinate defines |
|
// the center point. |
|
left = x - maxLineLength / 2; |
|
x -= lineWidths[0] / 2; |
|
} else if ( align === "right" ) { |
|
// The passed in x coordinate defines the |
|
// rightmost point of the text. |
|
left = x - maxLineLength; |
|
x -= lineWidths[0]; |
|
} else { |
|
throw new Error('Unrecognized alignment option, use "center" or "right".'); |
|
} |
|
prevX = x; |
|
text = da[0] + ") Tj\n"; |
|
for ( i = 1, len = da.length ; i < len; i++ ) { |
|
var delta = maxLineLength - lineWidths[i]; |
|
if( align === "center" ) delta /= 2; |
|
// T* = x-offset leading Td ( text ) |
|
text += ( ( left - prevX ) + delta ) + " -" + leading + " Td (" + da[i]; |
|
prevX = left + delta; |
|
if( i < len - 1 ) { |
|
text += ") Tj\n"; |
|
} |
|
} |
|
} else { |
|
text = da.join(") Tj\nT* ("); |
|
} |
|
} else { |
|
throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.'); |
|
} |
|
// Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates |
|
|
|
// BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET |
|
// if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations) |
|
// Thus, there is NO useful, *reliable* concept of "default" font for a page. |
|
// The fact that "default" (reuse font used before) font worked before in basic cases is an accident |
|
// - readers dealing smartly with brokenness of jsPDF's markup. |
|
|
|
var curY; |
|
|
|
if (todo){ |
|
//this.addPage(); |
|
//this._runningPageHeight += y - (activeFontSize * 1.7 / k); |
|
//curY = f2(pageHeight - activeFontSize * 1.7 /k); |
|
}else{ |
|
curY = f2((pageHeight - y) * k); |
|
} |
|
//curY = f2((pageHeight - (y - this._runningPageHeight)) * k); |
|
|
|
// if (curY < 0){ |
|
// console.log('auto page break'); |
|
// this.addPage(); |
|
// this._runningPageHeight = y - (activeFontSize * 1.7 / k); |
|
// curY = f2(pageHeight - activeFontSize * 1.7 /k); |
|
// } |
|
|
|
out( |
|
'BT\n/' + |
|
activeFontKey + ' ' + activeFontSize + ' Tf\n' + // font face, style, size |
|
(activeFontSize * lineHeightProportion) + ' TL\n' + // line spacing |
|
strokeOption +// stroke option |
|
textColor + |
|
'\n' + xtra + f2(x * k) + ' ' + curY + ' ' + mode + '\n(' + |
|
text + |
|
') Tj\nET'); |
|
|
|
if (todo) { |
|
//this.text( todo, x, activeFontSize * 1.7 / k); |
|
//this.text( todo, x, this._runningPageHeight + (activeFontSize * 1.7 / k)); |
|
this.text( todo, x, y);// + (activeFontSize * 1.7 / k)); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
API.lstext = function(text, x, y, spacing) { |
|
for (var i = 0, len = text.length ; i < len; i++, x += spacing) this.text(text[i], x, y); |
|
}; |
|
|
|
API.line = function(x1, y1, x2, y2) { |
|
return this.lines([[x2 - x1, y2 - y1]], x1, y1); |
|
}; |
|
|
|
API.clip = function() { |
|
// 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 |
|
}; |
|
|
|
/** |
|
* 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. 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. |
|
* @param {Boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name lines |
|
*/ |
|
API.lines = function(lines, x, y, scale, style, closed) { |
|
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') { |
|
tmp = y; |
|
y = x; |
|
x = lines; |
|
lines = tmp; |
|
} |
|
|
|
scale = scale || [1, 1]; |
|
|
|
// starting point |
|
out(f3(x * k) + ' ' + f3((pageHeight - y) * k) + ' m '); |
|
|
|
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 |
|
out(f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' l'); |
|
} 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 |
|
out( |
|
f3(x2 * k) + ' ' + |
|
f3((pageHeight - y2) * k) + ' ' + |
|
f3(x3 * k) + ' ' + |
|
f3((pageHeight - y3) * k) + ' ' + |
|
f3(x4 * k) + ' ' + |
|
f3((pageHeight - y4) * k) + ' c'); |
|
} |
|
} |
|
|
|
if (closed) { |
|
out(' h'); |
|
} |
|
|
|
// stroking / filling / both the path |
|
if (style !== null) { |
|
out(getStyle(style)); |
|
} |
|
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. 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. |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name rect |
|
*/ |
|
API.rect = function(x, y, w, h, style) { |
|
var op = getStyle(style); |
|
out([ |
|
f2(x * k), |
|
f2((pageHeight - y) * k), |
|
f2(w * k), |
|
f2(-h * k), |
|
're' |
|
].join(' ')); |
|
|
|
if (style !== null) { |
|
out(getStyle(style)); |
|
} |
|
|
|
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. 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. |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name triangle |
|
*/ |
|
API.triangle = function(x1, y1, x2, y2, x3, y3, style) { |
|
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); |
|
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} rx 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. 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. |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name roundedRect |
|
*/ |
|
API.roundedRect = function(x, y, w, h, rx, ry, style) { |
|
var MyArc = 4 / 3 * (Math.SQRT2 - 1); |
|
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); |
|
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} rx 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. 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. |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name ellipse |
|
*/ |
|
API.ellipse = function(x, y, rx, ry, style) { |
|
var lx = 4 / 3 * (Math.SQRT2 - 1) * rx, |
|
ly = 4 / 3 * (Math.SQRT2 - 1) * ry; |
|
|
|
out([ |
|
f2((x + rx) * k), |
|
f2((pageHeight - y) * k), |
|
'm', |
|
f2((x + rx) * k), |
|
f2((pageHeight - (y - ly)) * k), |
|
f2((x + lx) * k), |
|
f2((pageHeight - (y - ry)) * k), |
|
f2(x * k), |
|
f2((pageHeight - (y - ry)) * k), |
|
'c' |
|
].join(' ')); |
|
out([ |
|
f2((x - lx) * k), |
|
f2((pageHeight - (y - ry)) * k), |
|
f2((x - rx) * k), |
|
f2((pageHeight - (y - ly)) * k), |
|
f2((x - rx) * k), |
|
f2((pageHeight - y) * k), |
|
'c' |
|
].join(' ')); |
|
out([ |
|
f2((x - rx) * k), |
|
f2((pageHeight - (y + ly)) * k), |
|
f2((x - lx) * k), |
|
f2((pageHeight - (y + ry)) * k), |
|
f2(x * k), |
|
f2((pageHeight - (y + ry)) * k), |
|
'c' |
|
].join(' ')); |
|
out([ |
|
f2((x + lx) * k), |
|
f2((pageHeight - (y + ry)) * k), |
|
f2((x + rx) * k), |
|
f2((pageHeight - (y + ly)) * k), |
|
f2((x + rx) * k), |
|
f2((pageHeight - y) * k), |
|
'c' |
|
].join(' ')); |
|
|
|
if (style !== null) { |
|
out(getStyle(style)); |
|
} |
|
|
|
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. 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. |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name circle |
|
*/ |
|
API.circle = function(x, y, r, style) { |
|
return this.ellipse(x, y, r, r, style); |
|
}; |
|
|
|
/** |
|
* Adds a properties to the PDF document |
|
* |
|
* @param {Object} 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) { |
|
activeFontSize = size; |
|
return this; |
|
}; |
|
|
|
/** |
|
* 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 |
|
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 |
|
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} Postscript name of the Font. Example: "Menlo-Regular" |
|
* @param {String} Name of font-family from @font-face definition. Example: "Menlo Regular" |
|
* @param {String} Font style. Example: "normal" |
|
* @function |
|
* @returns the {fontKey} (same as the internal method) |
|
* @methodOf jsPDF# |
|
* @name addFont |
|
*/ |
|
API.addFont = function(postScriptName, fontName, fontStyle) { |
|
addFont(postScriptName, fontName, fontStyle, 'StandardEncoding'); |
|
}; |
|
|
|
/** |
|
* 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((width * k).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 |
|
* @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 color; |
|
if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) { |
|
// Gray color space. |
|
if (typeof ch1 === 'string') { |
|
color = ch1 + ' G'; |
|
} else { |
|
color = f2(ch1 / 255) + ' G'; |
|
} |
|
} else if (ch4 === undefined) { |
|
// RGB |
|
if (typeof ch1 === 'string') { |
|
color = [ch1, ch2, ch3, 'RG'].join(' '); |
|
} else { |
|
color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'RG'].join(' '); |
|
} |
|
} else { |
|
// CMYK |
|
if (typeof ch1 === 'string') { |
|
color = [ch1, ch2, ch3, ch4, 'K'].join(' '); |
|
} else { |
|
color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'K'].join(' '); |
|
} |
|
} |
|
|
|
out(color); |
|
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 |
|
* @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 color; |
|
|
|
if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) { |
|
// Gray color space. |
|
if (typeof ch1 === 'string') { |
|
color = ch1 + ' g'; |
|
} else { |
|
color = f2(ch1 / 255) + ' g'; |
|
} |
|
} else if (ch4 === undefined || typeof ch4 === 'object') { |
|
// RGB |
|
if (typeof ch1 === 'string') { |
|
color = [ch1, ch2, ch3, 'rg'].join(' '); |
|
} else { |
|
color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'rg'].join(' '); |
|
} |
|
if (ch4 && ch4.a === 0){ |
|
//TODO Implement transparency. |
|
//WORKAROUND use white for now |
|
color = ['255', '255', '255', 'rg'].join(' '); |
|
} |
|
} else { |
|
// CMYK |
|
if (typeof ch1 === 'string') { |
|
color = [ch1, ch2, ch3, ch4, 'k'].join(' '); |
|
} else { |
|
color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'k'].join(' '); |
|
} |
|
} |
|
|
|
out(color); |
|
return this; |
|
}; |
|
|
|
/** |
|
* Sets the text color for upcoming elements. |
|
* If only one, first argument is given, |
|
* treats the value as gray-scale color value. |
|
* |
|
* @param {Number} r Red channel color value in range 0-255 or {String} r color value in hexadecimal, example: '#FFFFFF' |
|
* @param {Number} g Green channel color value in range 0-255 |
|
* @param {Number} b Blue channel color value in range 0-255 |
|
* @function |
|
* @returns {jsPDF} |
|
* @methodOf jsPDF# |
|
* @name setTextColor |
|
*/ |
|
API.setTextColor = function(r, g, b) { |
|
if ((typeof r === 'string') && /^#[0-9A-Fa-f]{6}$/.test(r)) { |
|
var hex = parseInt(r.substr(1), 16); |
|
r = (hex >> 16) & 255; |
|
g = (hex >> 8) & 255; |
|
b = (hex & 255); |
|
} |
|
|
|
if ((r === 0 && g === 0 && b === 0) || (typeof g === 'undefined')) { |
|
textColor = f3(r / 255) + ' g'; |
|
} else { |
|
textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' '); |
|
} |
|
return this; |
|
}; |
|
|
|
/** |
|
* 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"); |
|