UNPKG

highcharts

Version:
1,388 lines (1,259 loc) 623 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.jspdf = factory()); }(this, function () { 'use strict'; var babelHelpers = {}; babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; babelHelpers; /** @preserve * jsPDF - PDF Document creation from JavaScript * Version 1.2.68 Built on 2017-07-18T14:26:05.034Z * CommitID 38732db74a * * Copyright (c) 2010-2014 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 * * Licensed under the MIT License * * 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 === 'undefined' ? 'undefined' : babelHelpers.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 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, documentProperties = { 'title': '', 'subject': '', 'author': '', 'keywords': '', 'creator': '' }, API = {}, events = new PubSub(API), ///////////////////// // Private functions ///////////////////// f2 = function f2(number) { return number.toFixed(2); // Ie, %.2f }, f3 = function f3(number) { return number.toFixed(3); // Ie, %.3f }, padd2 = function padd2(number) { return ('0' + parseInt(number)).slice(-2); }, padd2Hex = function padd2Hex(hexString) { var s = "00" + hexString; return s.substr(s.length - 2); }, out = function out(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 newObject() { // 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 newAdditionalObject() { 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 newObjectDeferred() { objectNumber++; offsets[objectNumber] = function () { return content_length; }; return objectNumber; }, newObjectDeferredBegin = function newObjectDeferredBegin(oid) { offsets[oid] = content_length; }, putStream = function putStream(str) { out('stream'); out(str); out('endstream'); }, putPages = function putPages() { 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('<</Type /Page'); out('/Parent 1 0 R'); out('/Resources 2 0 R'); out('/MediaBox [0 0 ' + f2(wPt) + ' ' + f2(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'); // 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)) p = new Matrix(k, 0, 0, -k, 0, pageHeight).toString() + " cm\n" + p; 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 += 3 + 2 * i + ' 0 R '; } out(kids + ']'); out('/Count ' + page); out('>>'); out('endobj'); events.publish('postPutPages'); }, putFont = function putFont(font) { font.objectNumber = newObject(); out('<</BaseFont/' + font.PostScriptName + '/Type/Font'); if (typeof font.encoding === 'string') { out('/Encoding/' + font.encoding); } out('/Subtype/Type1>>'); out('endobj'); }, putFonts = function putFonts() { for (var fontKey in fonts) { if (fonts.hasOwnProperty(fontKey)) { putFont(fonts[fontKey]); } } }, putXObject = function putXObject(xObject) { xObject.objectNumber = newObject(); out("<<"); out("/Type /XObject"); out("/Subtype /Form"); out("/BBox [" + [f2(xObject.x), f2(xObject.y), f2(xObject.x + xObject.width), f2(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 putXObjects() { for (var xObjectKey in renderTargets) { if (renderTargets.hasOwnProperty(xObjectKey)) { putXObject(renderTargets[xObjectKey]); } } }, interpolateAndEncodeRGBStream = function interpolateAndEncodeRGBStream(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 putShadingPattern(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 [" + f3(parseFloat(pattern.coords[0])) + " " // x1 + f3(parseFloat(pattern.coords[1])) + " "; // y1 if (pattern.type === 2) { // axial coords += f3(parseFloat(pattern.coords[2])) + " " // x2 + f3(parseFloat(pattern.coords[3])); // y2 } else { // radial coords += f3(parseFloat(pattern.coords[2])) + " " // r1 + f3(parseFloat(pattern.coords[3])) + " " // x2 + f3(parseFloat(pattern.coords[4])) + " " // y2 + f3(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 putTilingPattern(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(f3).join(" ") + "]"); out("/XStep " + f3(pattern.xStep)); out("/YStep " + f3(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 putPatterns() { 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 putGState(gState) { gState.objectNumber = newObject(); out("<<"); for (var p in gState) { switch (p) { case "opacity": out("/ca " + f2(gState[p])); break; } } out(">>"); out("endobj"); }, putGStates = function putGStates() { var gStateKey; for (gStateKey in gStates) { if (gStates.hasOwnProperty(gStateKey)) { putGState(gStates[gStateKey]); } } }, putXobjectDict = function putXobjectDict() { 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 putShadingPatternDict() { 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 putTilingPatternDict() { 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 putGStatesDict() { 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 putResourceDictionary() { 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 putResources() { 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 putAdditionalObjects() { 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 addToFontDictionary(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) * * @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 addFont(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 addFonts() { var HELVETICA = "helvetica", TIMES = "times", COURIER = "courier", NORMAL = "normal", BOLD = "bold", ITALIC = "italic", BOLD_ITALIC = "bolditalic", encoding = 'StandardEncoding', ZAPF = "zapfdingbats", 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], ['ZapfDingbats', ZAPF]]; 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 }); }, matrixMult = function matrixMult(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 Matrix(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 toString() { return [f3(this.a), f3(this.b), f3(this.c), f3(this.d), f3(this.e), f3(this.f)].join(" "); } }; 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 RenderTarget() { 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 restore() { 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 beginNewRenderTarget(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 endFormObject(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 addPattern(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 addGState(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 to8bitStream(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 pdfEscape(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 putInfo() { 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 putCatalog() { 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 putTrailer() { out('/Size ' + (objectNumber + 1)); out('/Root ' + objectNumber + ' 0 R'); out('/Info ' + (objectNumber - 1) + ' 0 R'); }, beginPage = function beginPage(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; // } // TODO: What is the reason for this (for me it only seems to raise bugs)? // 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 _addPage() { beginPage.apply(this, arguments); // Set line width out(f2(lineWidth) + ' 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 _deletePage(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 _setPage(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 _getFont(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': case 'helvetica': fontName = 'helvetica'; break; case 'fixed': case 'monospace': case 'terminal': case 'courier': 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 buildDocument() { 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 getStyle(style) { // see path-painting operators in PDF spec var op = 'n'; // none 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 putStyle(style, patternKey, patternData) { style = getStyle(style); // stroking / filling / both the path if (!patternKey) { out(style); return; } patternData || (patternData = unitMatrix); var patternId = patternMap[patternKey]; var pattern = patterns[patternId]; if (pattern instanceof API.ShadingPattern) { out("q"); out("W " + style); if (pattern.gState) { API.setGState(pattern.gState); } out(patternData.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(patternKey, 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"); } }, getArrayBuffer = function getArrayBuffer() { 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 getBlob() { 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);