jspdf.js

  1. /* eslint-disable no-console */
  2. // @if MODULE_FORMAT!='cjs'
  3. import { saveAs } from "./libs/FileSaver.js";
  4. // @endif
  5. import { globalObject } from "./libs/globalObject.js";
  6. import { RGBColor } from "./libs/rgbcolor.js";
  7. import { btoa } from "./libs/AtobBtoa.js";
  8. import { console } from "./libs/console.js";
  9. import { PDFSecurity } from "./libs/pdfsecurity.js";
  10. import { toPDFName } from "./libs/pdfname.js";
  11. /**
  12. * jsPDF's Internal PubSub Implementation.
  13. * Backward compatible rewritten on 2014 by
  14. * Diego Casorran, https://github.com/diegocr
  15. *
  16. * @class
  17. * @name PubSub
  18. * @ignore
  19. */
  20. function PubSub(context) {
  21. if (typeof context !== "object") {
  22. throw new Error(
  23. "Invalid Context passed to initialize PubSub (jsPDF-module)"
  24. );
  25. }
  26. var topics = {};
  27. this.subscribe = function(topic, callback, once) {
  28. once = once || false;
  29. if (
  30. typeof topic !== "string" ||
  31. typeof callback !== "function" ||
  32. typeof once !== "boolean"
  33. ) {
  34. throw new Error(
  35. "Invalid arguments passed to PubSub.subscribe (jsPDF-module)"
  36. );
  37. }
  38. if (!topics.hasOwnProperty(topic)) {
  39. topics[topic] = {};
  40. }
  41. var token = Math.random().toString(35);
  42. topics[topic][token] = [callback, !!once];
  43. return token;
  44. };
  45. this.unsubscribe = function(token) {
  46. for (var topic in topics) {
  47. if (topics[topic][token]) {
  48. delete topics[topic][token];
  49. if (Object.keys(topics[topic]).length === 0) {
  50. delete topics[topic];
  51. }
  52. return true;
  53. }
  54. }
  55. return false;
  56. };
  57. this.publish = function(topic) {
  58. if (topics.hasOwnProperty(topic)) {
  59. var args = Array.prototype.slice.call(arguments, 1),
  60. tokens = [];
  61. for (var token in topics[topic]) {
  62. var sub = topics[topic][token];
  63. try {
  64. sub[0].apply(context, args);
  65. } catch (ex) {
  66. if (globalObject.console) {
  67. console.error("jsPDF PubSub Error", ex.message, ex);
  68. }
  69. }
  70. if (sub[1]) tokens.push(token);
  71. }
  72. if (tokens.length) tokens.forEach(this.unsubscribe);
  73. }
  74. };
  75. this.getTopics = function() {
  76. return topics;
  77. };
  78. }
  79. function GState(parameters) {
  80. if (!(this instanceof GState)) {
  81. return new GState(parameters);
  82. }
  83. /**
  84. * @name GState#opacity
  85. * @type {any}
  86. */
  87. /**
  88. * @name GState#stroke-opacity
  89. * @type {any}
  90. */
  91. var supported = "opacity,stroke-opacity".split(",");
  92. for (var p in parameters) {
  93. if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) {
  94. this[p] = parameters[p];
  95. }
  96. }
  97. /**
  98. * @name GState#id
  99. * @type {string}
  100. */
  101. this.id = ""; // set by addGState()
  102. /**
  103. * @name GState#objectNumber
  104. * @type {number}
  105. */
  106. this.objectNumber = -1; // will be set by putGState()
  107. }
  108. GState.prototype.equals = function equals(other) {
  109. var ignore = "id,objectNumber,equals";
  110. var p;
  111. if (!other || typeof other !== typeof this) return false;
  112. var count = 0;
  113. for (p in this) {
  114. if (ignore.indexOf(p) >= 0) continue;
  115. if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false;
  116. if (this[p] !== other[p]) return false;
  117. count++;
  118. }
  119. for (p in other) {
  120. if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--;
  121. }
  122. return count === 0;
  123. };
  124. function Pattern(gState, matrix) {
  125. this.gState = gState;
  126. this.matrix = matrix;
  127. this.id = ""; // set by addPattern()
  128. this.objectNumber = -1; // will be set by putPattern()
  129. }
  130. function ShadingPattern(type, coords, colors, gState, matrix) {
  131. if (!(this instanceof ShadingPattern)) {
  132. return new ShadingPattern(type, coords, colors, gState, matrix);
  133. }
  134. // see putPattern() for information how they are realized
  135. this.type = type === "axial" ? 2 : 3;
  136. this.coords = coords;
  137. this.colors = colors;
  138. Pattern.call(this, gState, matrix);
  139. }
  140. function TilingPattern(boundingBox, xStep, yStep, gState, matrix) {
  141. if (!(this instanceof TilingPattern)) {
  142. return new TilingPattern(boundingBox, xStep, yStep, gState, matrix);
  143. }
  144. this.boundingBox = boundingBox;
  145. this.xStep = xStep;
  146. this.yStep = yStep;
  147. this.stream = ""; // set by endTilingPattern();
  148. this.cloneIndex = 0;
  149. Pattern.call(this, gState, matrix);
  150. }
  151. /**
  152. * Creates new jsPDF document object instance.
  153. * @name jsPDF
  154. * @class
  155. * @param {Object} [options] - Collection of settings initializing the jsPDF-instance
  156. * @param {string} [options.orientation=portrait] - Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l").<br />
  157. * @param {string} [options.unit=mm] Measurement unit (base unit) to be used when coordinates are specified.<br />
  158. * Possible values are "pt" (points), "mm", "cm", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px"
  159. * units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"].
  160. * @param {string/Array} [options.format=a4] The format of the first page. Can be:<ul><li>a0 - a10</li><li>b0 - b10</li><li>c0 - c10</li><li>dl</li><li>letter</li><li>government-letter</li><li>legal</li><li>junior-legal</li><li>ledger</li><li>tabloid</li><li>credit-card</li></ul><br />
  161. * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89]
  162. * @param {boolean} [options.putOnlyUsedFonts=false] Only put fonts into the PDF, which were used.
  163. * @param {boolean} [options.compress=false] Compress the generated PDF.
  164. * @param {number} [options.precision=16] Precision of the element-positions.
  165. * @param {number} [options.userUnit=1.0] Not to be confused with the base unit. Please inform yourself before you use it.
  166. * @param {string[]} [options.hotfixes] An array of strings to enable hotfixes such as correct pixel scaling.
  167. * @param {Object} [options.encryption]
  168. * @param {string} [options.encryption.userPassword] Password for the user bound by the given permissions list.
  169. * @param {string} [options.encryption.ownerPassword] Both userPassword and ownerPassword should be set for proper authentication.
  170. * @param {string[]} [options.encryption.userPermissions] Array of permissions "print", "modify", "copy", "annot-forms", accessible by the user.
  171. * @param {number|"smart"} [options.floatPrecision=16]
  172. * @returns {jsPDF} jsPDF-instance
  173. * @description
  174. * ```
  175. * {
  176. * orientation: 'p',
  177. * unit: 'mm',
  178. * format: 'a4',
  179. * putOnlyUsedFonts:true,
  180. * floatPrecision: 16 // or "smart", default is 16
  181. * }
  182. * ```
  183. *
  184. * @constructor
  185. */
  186. function jsPDF(options) {
  187. var orientation = typeof arguments[0] === "string" ? arguments[0] : "p";
  188. var unit = arguments[1];
  189. var format = arguments[2];
  190. var compressPdf = arguments[3];
  191. var filters = [];
  192. var userUnit = 1.0;
  193. var precision;
  194. var floatPrecision = 16;
  195. var defaultPathOperation = "S";
  196. var encryptionOptions = null;
  197. options = options || {};
  198. if (typeof options === "object") {
  199. orientation = options.orientation;
  200. unit = options.unit || unit;
  201. format = options.format || format;
  202. compressPdf = options.compress || options.compressPdf || compressPdf;
  203. encryptionOptions = options.encryption || null;
  204. if (encryptionOptions !== null) {
  205. encryptionOptions.userPassword = encryptionOptions.userPassword || "";
  206. encryptionOptions.ownerPassword = encryptionOptions.ownerPassword || "";
  207. encryptionOptions.userPermissions =
  208. encryptionOptions.userPermissions || [];
  209. }
  210. userUnit =
  211. typeof options.userUnit === "number" ? Math.abs(options.userUnit) : 1.0;
  212. if (typeof options.precision !== "undefined") {
  213. precision = options.precision;
  214. }
  215. if (typeof options.floatPrecision !== "undefined") {
  216. floatPrecision = options.floatPrecision;
  217. }
  218. defaultPathOperation = options.defaultPathOperation || "S";
  219. }
  220. filters =
  221. options.filters || (compressPdf === true ? ["FlateEncode"] : filters);
  222. unit = unit || "mm";
  223. orientation = ("" + (orientation || "P")).toLowerCase();
  224. var putOnlyUsedFonts = options.putOnlyUsedFonts || false;
  225. var usedFonts = {};
  226. var API = {
  227. internal: {},
  228. __private__: {}
  229. };
  230. API.__private__.PubSub = PubSub;
  231. var pdfVersion = "1.3";
  232. var getPdfVersion = (API.__private__.getPdfVersion = function() {
  233. return pdfVersion;
  234. });
  235. API.__private__.setPdfVersion = function(value) {
  236. pdfVersion = value;
  237. };
  238. // Size in pt of various paper formats
  239. var pageFormats = {
  240. a0: [2383.94, 3370.39],
  241. a1: [1683.78, 2383.94],
  242. a2: [1190.55, 1683.78],
  243. a3: [841.89, 1190.55],
  244. a4: [595.28, 841.89],
  245. a5: [419.53, 595.28],
  246. a6: [297.64, 419.53],
  247. a7: [209.76, 297.64],
  248. a8: [147.4, 209.76],
  249. a9: [104.88, 147.4],
  250. a10: [73.7, 104.88],
  251. b0: [2834.65, 4008.19],
  252. b1: [2004.09, 2834.65],
  253. b2: [1417.32, 2004.09],
  254. b3: [1000.63, 1417.32],
  255. b4: [708.66, 1000.63],
  256. b5: [498.9, 708.66],
  257. b6: [354.33, 498.9],
  258. b7: [249.45, 354.33],
  259. b8: [175.75, 249.45],
  260. b9: [124.72, 175.75],
  261. b10: [87.87, 124.72],
  262. c0: [2599.37, 3676.54],
  263. c1: [1836.85, 2599.37],
  264. c2: [1298.27, 1836.85],
  265. c3: [918.43, 1298.27],
  266. c4: [649.13, 918.43],
  267. c5: [459.21, 649.13],
  268. c6: [323.15, 459.21],
  269. c7: [229.61, 323.15],
  270. c8: [161.57, 229.61],
  271. c9: [113.39, 161.57],
  272. c10: [79.37, 113.39],
  273. dl: [311.81, 623.62],
  274. letter: [612, 792],
  275. "government-letter": [576, 756],
  276. legal: [612, 1008],
  277. "junior-legal": [576, 360],
  278. ledger: [1224, 792],
  279. tabloid: [792, 1224],
  280. "credit-card": [153, 243]
  281. };
  282. API.__private__.getPageFormats = function() {
  283. return pageFormats;
  284. };
  285. var getPageFormat = (API.__private__.getPageFormat = function(value) {
  286. return pageFormats[value];
  287. });
  288. format = format || "a4";
  289. var ApiMode = {
  290. COMPAT: "compat",
  291. ADVANCED: "advanced"
  292. };
  293. var apiMode = ApiMode.COMPAT;
  294. function advancedAPI() {
  295. // prepend global change of basis matrix
  296. // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix
  297. // that does this job for us (however, texts, images and similar objects must be drawn bottom up))
  298. this.saveGraphicsState();
  299. out(
  300. new Matrix(
  301. scaleFactor,
  302. 0,
  303. 0,
  304. -scaleFactor,
  305. 0,
  306. getPageHeight() * scaleFactor
  307. ).toString() + " cm"
  308. );
  309. this.setFontSize(this.getFontSize() / scaleFactor);
  310. // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation
  311. // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here.
  312. defaultPathOperation = "n";
  313. apiMode = ApiMode.ADVANCED;
  314. }
  315. function compatAPI() {
  316. this.restoreGraphicsState();
  317. defaultPathOperation = "S";
  318. apiMode = ApiMode.COMPAT;
  319. }
  320. /**
  321. * @function combineFontStyleAndFontWeight
  322. * @param {string} fontStyle Fontstyle or variant. Example: "italic".
  323. * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400
  324. * @returns {string}
  325. * @private
  326. */
  327. var combineFontStyleAndFontWeight = (API.__private__.combineFontStyleAndFontWeight = function(
  328. fontStyle,
  329. fontWeight
  330. ) {
  331. if (
  332. (fontStyle == "bold" && fontWeight == "normal") ||
  333. (fontStyle == "bold" && fontWeight == 400) ||
  334. (fontStyle == "normal" && fontWeight == "italic") ||
  335. (fontStyle == "bold" && fontWeight == "italic")
  336. ) {
  337. throw new Error("Invalid Combination of fontweight and fontstyle");
  338. }
  339. if (fontWeight) {
  340. fontStyle =
  341. fontWeight == 400 || fontWeight === "normal"
  342. ? fontStyle === "italic"
  343. ? "italic"
  344. : "normal"
  345. : (fontWeight == 700 || fontWeight === "bold") &&
  346. fontStyle === "normal"
  347. ? "bold"
  348. : (fontWeight == 700 ? "bold" : fontWeight) + "" + fontStyle;
  349. }
  350. return fontStyle;
  351. });
  352. /**
  353. * @callback ApiSwitchBody
  354. * @param {jsPDF} pdf
  355. */
  356. /**
  357. * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual
  358. * screen coordinates and the PDF coordinate system.
  359. * - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms
  360. * - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might
  361. * not support this mode, though.
  362. * Initial mode is "compat".
  363. *
  364. * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to
  365. * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}.
  366. *
  367. * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the
  368. * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject}
  369. * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode.
  370. *
  371. * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
  372. * The API mode will be switched back automatically afterwards.
  373. * @returns {jsPDF}
  374. * @memberof jsPDF#
  375. * @name advancedAPI
  376. */
  377. API.advancedAPI = function(body) {
  378. var doSwitch = apiMode === ApiMode.COMPAT;
  379. if (doSwitch) {
  380. advancedAPI.call(this);
  381. }
  382. if (typeof body !== "function") {
  383. return this;
  384. }
  385. body(this);
  386. if (doSwitch) {
  387. compatAPI.call(this);
  388. }
  389. return this;
  390. };
  391. /**
  392. * Switches to "compat" API mode. See {@link advancedAPI} for more details.
  393. *
  394. * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
  395. * The API mode will be switched back automatically afterwards.
  396. * @return {jsPDF}
  397. * @memberof jsPDF#
  398. * @name compatApi
  399. */
  400. API.compatAPI = function(body) {
  401. var doSwitch = apiMode === ApiMode.ADVANCED;
  402. if (doSwitch) {
  403. compatAPI.call(this);
  404. }
  405. if (typeof body !== "function") {
  406. return this;
  407. }
  408. body(this);
  409. if (doSwitch) {
  410. advancedAPI.call(this);
  411. }
  412. return this;
  413. };
  414. /**
  415. * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}.
  416. * @memberof jsPDF#
  417. * @name isAdvancedAPI
  418. */
  419. API.isAdvancedAPI = function() {
  420. return apiMode === ApiMode.ADVANCED;
  421. };
  422. var advancedApiModeTrap = function(methodName) {
  423. if (apiMode !== ApiMode.ADVANCED) {
  424. throw new Error(
  425. methodName +
  426. " is only available in 'advanced' API mode. " +
  427. "You need to call advancedAPI() first."
  428. );
  429. }
  430. };
  431. var roundToPrecision = (API.roundToPrecision = API.__private__.roundToPrecision = function(
  432. number,
  433. parmPrecision
  434. ) {
  435. var tmpPrecision = precision || parmPrecision;
  436. if (isNaN(number) || isNaN(tmpPrecision)) {
  437. throw new Error("Invalid argument passed to jsPDF.roundToPrecision");
  438. }
  439. return number.toFixed(tmpPrecision).replace(/0+$/, "");
  440. });
  441. // high precision float
  442. var hpf;
  443. if (typeof floatPrecision === "number") {
  444. hpf = API.hpf = API.__private__.hpf = function(number) {
  445. if (isNaN(number)) {
  446. throw new Error("Invalid argument passed to jsPDF.hpf");
  447. }
  448. return roundToPrecision(number, floatPrecision);
  449. };
  450. } else if (floatPrecision === "smart") {
  451. hpf = API.hpf = API.__private__.hpf = function(number) {
  452. if (isNaN(number)) {
  453. throw new Error("Invalid argument passed to jsPDF.hpf");
  454. }
  455. if (number > -1 && number < 1) {
  456. return roundToPrecision(number, 16);
  457. } else {
  458. return roundToPrecision(number, 5);
  459. }
  460. };
  461. } else {
  462. hpf = API.hpf = API.__private__.hpf = function(number) {
  463. if (isNaN(number)) {
  464. throw new Error("Invalid argument passed to jsPDF.hpf");
  465. }
  466. return roundToPrecision(number, 16);
  467. };
  468. }
  469. var f2 = (API.f2 = API.__private__.f2 = function(number) {
  470. if (isNaN(number)) {
  471. throw new Error("Invalid argument passed to jsPDF.f2");
  472. }
  473. return roundToPrecision(number, 2);
  474. });
  475. var f3 = (API.__private__.f3 = function(number) {
  476. if (isNaN(number)) {
  477. throw new Error("Invalid argument passed to jsPDF.f3");
  478. }
  479. return roundToPrecision(number, 3);
  480. });
  481. var scale = (API.scale = API.__private__.scale = function(number) {
  482. if (isNaN(number)) {
  483. throw new Error("Invalid argument passed to jsPDF.scale");
  484. }
  485. if (apiMode === ApiMode.COMPAT) {
  486. return number * scaleFactor;
  487. } else if (apiMode === ApiMode.ADVANCED) {
  488. return number;
  489. }
  490. });
  491. var transformY = function(y) {
  492. if (apiMode === ApiMode.COMPAT) {
  493. return getPageHeight() - y;
  494. } else if (apiMode === ApiMode.ADVANCED) {
  495. return y;
  496. }
  497. };
  498. var transformScaleY = function(y) {
  499. return scale(transformY(y));
  500. };
  501. /**
  502. * @name setPrecision
  503. * @memberof jsPDF#
  504. * @function
  505. * @instance
  506. * @param {string} precision
  507. * @returns {jsPDF}
  508. */
  509. API.__private__.setPrecision = API.setPrecision = function(value) {
  510. if (typeof parseInt(value, 10) === "number") {
  511. precision = parseInt(value, 10);
  512. }
  513. };
  514. var fileId = "00000000000000000000000000000000";
  515. var getFileId = (API.__private__.getFileId = function() {
  516. return fileId;
  517. });
  518. var setFileId = (API.__private__.setFileId = function(value) {
  519. if (typeof value !== "undefined" && /^[a-fA-F0-9]{32}$/.test(value)) {
  520. fileId = value.toUpperCase();
  521. } else {
  522. fileId = fileId
  523. .split("")
  524. .map(function() {
  525. return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16));
  526. })
  527. .join("");
  528. }
  529. if (encryptionOptions !== null) {
  530. encryption = new PDFSecurity(
  531. encryptionOptions.userPermissions,
  532. encryptionOptions.userPassword,
  533. encryptionOptions.ownerPassword,
  534. fileId
  535. );
  536. }
  537. return fileId;
  538. });
  539. /**
  540. * @name setFileId
  541. * @memberof jsPDF#
  542. * @function
  543. * @instance
  544. * @param {string} value GUID.
  545. * @returns {jsPDF}
  546. */
  547. API.setFileId = function(value) {
  548. setFileId(value);
  549. return this;
  550. };
  551. /**
  552. * @name getFileId
  553. * @memberof jsPDF#
  554. * @function
  555. * @instance
  556. *
  557. * @returns {string} GUID.
  558. */
  559. API.getFileId = function() {
  560. return getFileId();
  561. };
  562. var creationDate;
  563. var convertDateToPDFDate = (API.__private__.convertDateToPDFDate = function(
  564. parmDate
  565. ) {
  566. var result = "";
  567. var tzoffset = parmDate.getTimezoneOffset(),
  568. tzsign = tzoffset < 0 ? "+" : "-",
  569. tzhour = Math.floor(Math.abs(tzoffset / 60)),
  570. tzmin = Math.abs(tzoffset % 60),
  571. timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join("");
  572. result = [
  573. "D:",
  574. parmDate.getFullYear(),
  575. padd2(parmDate.getMonth() + 1),
  576. padd2(parmDate.getDate()),
  577. padd2(parmDate.getHours()),
  578. padd2(parmDate.getMinutes()),
  579. padd2(parmDate.getSeconds()),
  580. timeZoneString
  581. ].join("");
  582. return result;
  583. });
  584. var convertPDFDateToDate = (API.__private__.convertPDFDateToDate = function(
  585. parmPDFDate
  586. ) {
  587. var year = parseInt(parmPDFDate.substr(2, 4), 10);
  588. var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1;
  589. var date = parseInt(parmPDFDate.substr(8, 2), 10);
  590. var hour = parseInt(parmPDFDate.substr(10, 2), 10);
  591. var minutes = parseInt(parmPDFDate.substr(12, 2), 10);
  592. var seconds = parseInt(parmPDFDate.substr(14, 2), 10);
  593. // var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10);
  594. // var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10);
  595. var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0);
  596. return resultingDate;
  597. });
  598. var setCreationDate = (API.__private__.setCreationDate = function(date) {
  599. var tmpCreationDateString;
  600. var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/;
  601. if (typeof date === "undefined") {
  602. date = new Date();
  603. }
  604. if (date instanceof Date) {
  605. tmpCreationDateString = convertDateToPDFDate(date);
  606. } else if (regexPDFCreationDate.test(date)) {
  607. tmpCreationDateString = date;
  608. } else {
  609. throw new Error("Invalid argument passed to jsPDF.setCreationDate");
  610. }
  611. creationDate = tmpCreationDateString;
  612. return creationDate;
  613. });
  614. var getCreationDate = (API.__private__.getCreationDate = function(type) {
  615. var result = creationDate;
  616. if (type === "jsDate") {
  617. result = convertPDFDateToDate(creationDate);
  618. }
  619. return result;
  620. });
  621. /**
  622. * @name setCreationDate
  623. * @memberof jsPDF#
  624. * @function
  625. * @instance
  626. * @param {Object} date
  627. * @returns {jsPDF}
  628. */
  629. API.setCreationDate = function(date) {
  630. setCreationDate(date);
  631. return this;
  632. };
  633. /**
  634. * @name getCreationDate
  635. * @memberof jsPDF#
  636. * @function
  637. * @instance
  638. * @param {Object} type
  639. * @returns {Object}
  640. */
  641. API.getCreationDate = function(type) {
  642. return getCreationDate(type);
  643. };
  644. var padd2 = (API.__private__.padd2 = function(number) {
  645. return ("0" + parseInt(number)).slice(-2);
  646. });
  647. var padd2Hex = (API.__private__.padd2Hex = function(hexString) {
  648. hexString = hexString.toString();
  649. return ("00" + hexString).substr(hexString.length);
  650. });
  651. var objectNumber = 0; // 'n' Current object number
  652. var offsets = []; // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes.
  653. var content = [];
  654. var contentLength = 0;
  655. var additionalObjects = [];
  656. var pages = [];
  657. var currentPage;
  658. var hasCustomDestination = false;
  659. var outputDestination = content;
  660. var resetDocument = function() {
  661. //reset fields relevant for objectNumber generation and xref.
  662. objectNumber = 0;
  663. contentLength = 0;
  664. content = [];
  665. offsets = [];
  666. additionalObjects = [];
  667. rootDictionaryObjId = newObjectDeferred();
  668. resourceDictionaryObjId = newObjectDeferred();
  669. };
  670. API.__private__.setCustomOutputDestination = function(destination) {
  671. hasCustomDestination = true;
  672. outputDestination = destination;
  673. };
  674. var setOutputDestination = function(destination) {
  675. if (!hasCustomDestination) {
  676. outputDestination = destination;
  677. }
  678. };
  679. API.__private__.resetCustomOutputDestination = function() {
  680. hasCustomDestination = false;
  681. outputDestination = content;
  682. };
  683. var out = (API.__private__.out = function(string) {
  684. string = string.toString();
  685. contentLength += string.length + 1;
  686. outputDestination.push(string);
  687. return outputDestination;
  688. });
  689. var write = (API.__private__.write = function(value) {
  690. return out(
  691. arguments.length === 1
  692. ? value.toString()
  693. : Array.prototype.join.call(arguments, " ")
  694. );
  695. });
  696. var getArrayBuffer = (API.__private__.getArrayBuffer = function(data) {
  697. var len = data.length,
  698. ab = new ArrayBuffer(len),
  699. u8 = new Uint8Array(ab);
  700. while (len--) u8[len] = data.charCodeAt(len);
  701. return ab;
  702. });
  703. var standardFonts = [
  704. ["Helvetica", "helvetica", "normal", "WinAnsiEncoding"],
  705. ["Helvetica-Bold", "helvetica", "bold", "WinAnsiEncoding"],
  706. ["Helvetica-Oblique", "helvetica", "italic", "WinAnsiEncoding"],
  707. ["Helvetica-BoldOblique", "helvetica", "bolditalic", "WinAnsiEncoding"],
  708. ["Courier", "courier", "normal", "WinAnsiEncoding"],
  709. ["Courier-Bold", "courier", "bold", "WinAnsiEncoding"],
  710. ["Courier-Oblique", "courier", "italic", "WinAnsiEncoding"],
  711. ["Courier-BoldOblique", "courier", "bolditalic", "WinAnsiEncoding"],
  712. ["Times-Roman", "times", "normal", "WinAnsiEncoding"],
  713. ["Times-Bold", "times", "bold", "WinAnsiEncoding"],
  714. ["Times-Italic", "times", "italic", "WinAnsiEncoding"],
  715. ["Times-BoldItalic", "times", "bolditalic", "WinAnsiEncoding"],
  716. ["ZapfDingbats", "zapfdingbats", "normal", null],
  717. ["Symbol", "symbol", "normal", null]
  718. ];
  719. API.__private__.getStandardFonts = function() {
  720. return standardFonts;
  721. };
  722. var activeFontSize = options.fontSize || 16;
  723. /**
  724. * Sets font size for upcoming text elements.
  725. *
  726. * @param {number} size Font size in points.
  727. * @function
  728. * @instance
  729. * @returns {jsPDF}
  730. * @memberof jsPDF#
  731. * @name setFontSize
  732. */
  733. API.__private__.setFontSize = API.setFontSize = function(size) {
  734. if (apiMode === ApiMode.ADVANCED) {
  735. activeFontSize = size / scaleFactor;
  736. } else {
  737. activeFontSize = size;
  738. }
  739. return this;
  740. };
  741. /**
  742. * Gets the fontsize for upcoming text elements.
  743. *
  744. * @function
  745. * @instance
  746. * @returns {number}
  747. * @memberof jsPDF#
  748. * @name getFontSize
  749. */
  750. var getFontSize = (API.__private__.getFontSize = API.getFontSize = function() {
  751. if (apiMode === ApiMode.COMPAT) {
  752. return activeFontSize;
  753. } else {
  754. return activeFontSize * scaleFactor;
  755. }
  756. });
  757. var R2L = options.R2L || false;
  758. /**
  759. * Set value of R2L functionality.
  760. *
  761. * @param {boolean} value
  762. * @function
  763. * @instance
  764. * @returns {jsPDF} jsPDF-instance
  765. * @memberof jsPDF#
  766. * @name setR2L
  767. */
  768. API.__private__.setR2L = API.setR2L = function(value) {
  769. R2L = value;
  770. return this;
  771. };
  772. /**
  773. * Get value of R2L functionality.
  774. *
  775. * @function
  776. * @instance
  777. * @returns {boolean} jsPDF-instance
  778. * @memberof jsPDF#
  779. * @name getR2L
  780. */
  781. API.__private__.getR2L = API.getR2L = function() {
  782. return R2L;
  783. };
  784. var zoomMode; // default: 1;
  785. var setZoomMode = (API.__private__.setZoomMode = function(zoom) {
  786. var validZoomModes = [
  787. undefined,
  788. null,
  789. "fullwidth",
  790. "fullheight",
  791. "fullpage",
  792. "original"
  793. ];
  794. if (/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(zoom)) {
  795. zoomMode = zoom;
  796. } else if (!isNaN(zoom)) {
  797. zoomMode = parseInt(zoom, 10);
  798. } else if (validZoomModes.indexOf(zoom) !== -1) {
  799. zoomMode = zoom;
  800. } else {
  801. throw new Error(
  802. 'zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "' +
  803. zoom +
  804. '" is not recognized.'
  805. );
  806. }
  807. });
  808. API.__private__.getZoomMode = function() {
  809. return zoomMode;
  810. };
  811. var pageMode; // default: 'UseOutlines';
  812. var setPageMode = (API.__private__.setPageMode = function(pmode) {
  813. var validPageModes = [
  814. undefined,
  815. null,
  816. "UseNone",
  817. "UseOutlines",
  818. "UseThumbs",
  819. "FullScreen"
  820. ];
  821. if (validPageModes.indexOf(pmode) == -1) {
  822. throw new Error(
  823. 'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' +
  824. pmode +
  825. '" is not recognized.'
  826. );
  827. }
  828. pageMode = pmode;
  829. });
  830. API.__private__.getPageMode = function() {
  831. return pageMode;
  832. };
  833. var layoutMode; // default: 'continuous';
  834. var setLayoutMode = (API.__private__.setLayoutMode = function(layout) {
  835. var validLayoutModes = [
  836. undefined,
  837. null,
  838. "continuous",
  839. "single",
  840. "twoleft",
  841. "tworight",
  842. "two"
  843. ];
  844. if (validLayoutModes.indexOf(layout) == -1) {
  845. throw new Error(
  846. 'Layout mode must be one of continuous, single, twoleft, tworight. "' +
  847. layout +
  848. '" is not recognized.'
  849. );
  850. }
  851. layoutMode = layout;
  852. });
  853. API.__private__.getLayoutMode = function() {
  854. return layoutMode;
  855. };
  856. /**
  857. * Set the display mode options of the page like zoom and layout.
  858. *
  859. * @name setDisplayMode
  860. * @memberof jsPDF#
  861. * @function
  862. * @instance
  863. * @param {integer|String} zoom You can pass an integer or percentage as
  864. * a string. 2 will scale the document up 2x, '200%' will scale up by the
  865. * same amount. You can also set it to 'fullwidth', 'fullheight',
  866. * 'fullpage', or 'original'.
  867. *
  868. * Only certain PDF readers support this, such as Adobe Acrobat.
  869. *
  870. * @param {string} layout Layout mode can be: 'continuous' - this is the
  871. * default continuous scroll. 'single' - the single page mode only shows one
  872. * page at a time. 'twoleft' - two column left mode, first page starts on
  873. * the left, and 'tworight' - pages are laid out in two columns, with the
  874. * first page on the right. This would be used for books.
  875. * @param {string} pmode 'UseOutlines' - it shows the
  876. * outline of the document on the left. 'UseThumbs' - shows thumbnails along
  877. * the left. 'FullScreen' - prompts the user to enter fullscreen mode.
  878. *
  879. * @returns {jsPDF}
  880. */
  881. API.__private__.setDisplayMode = API.setDisplayMode = function(
  882. zoom,
  883. layout,
  884. pmode
  885. ) {
  886. setZoomMode(zoom);
  887. setLayoutMode(layout);
  888. setPageMode(pmode);
  889. return this;
  890. };
  891. var documentProperties = {
  892. title: "",
  893. subject: "",
  894. author: "",
  895. keywords: "",
  896. creator: ""
  897. };
  898. API.__private__.getDocumentProperty = function(key) {
  899. if (Object.keys(documentProperties).indexOf(key) === -1) {
  900. throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");
  901. }
  902. return documentProperties[key];
  903. };
  904. API.__private__.getDocumentProperties = function() {
  905. return documentProperties;
  906. };
  907. /**
  908. * Adds a properties to the PDF document.
  909. *
  910. * @param {Object} A property_name-to-property_value object structure.
  911. * @function
  912. * @instance
  913. * @returns {jsPDF}
  914. * @memberof jsPDF#
  915. * @name setDocumentProperties
  916. */
  917. API.__private__.setDocumentProperties = API.setProperties = API.setDocumentProperties = function(
  918. properties
  919. ) {
  920. // copying only those properties we can render.
  921. for (var property in documentProperties) {
  922. if (documentProperties.hasOwnProperty(property) && properties[property]) {
  923. documentProperties[property] = properties[property];
  924. }
  925. }
  926. return this;
  927. };
  928. API.__private__.setDocumentProperty = function(key, value) {
  929. if (Object.keys(documentProperties).indexOf(key) === -1) {
  930. throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty");
  931. }
  932. return (documentProperties[key] = value);
  933. };
  934. var fonts = {}; // collection of font objects, where key is fontKey - a dynamically created label for a given font.
  935. var fontmap = {}; // mapping structure fontName > fontStyle > font key - performance layer. See addFont()
  936. var activeFontKey; // will be string representing the KEY of the font as combination of fontName + fontStyle
  937. var fontStateStack = []; //
  938. var patterns = {}; // collection of pattern objects
  939. var patternMap = {}; // see fonts
  940. var gStates = {}; // collection of graphic state objects
  941. var gStatesMap = {}; // see fonts
  942. var activeGState = null;
  943. var scaleFactor; // Scale factor
  944. var page = 0;
  945. var pagesContext = [];
  946. var events = new PubSub(API);
  947. var hotfixes = options.hotfixes || [];
  948. var renderTargets = {};
  949. var renderTargetMap = {};
  950. var renderTargetStack = [];
  951. var pageX;
  952. var pageY;
  953. var pageMatrix; // only used for FormObjects
  954. /**
  955. * A matrix object for 2D homogenous transformations: <br>
  956. * | a b 0 | <br>
  957. * | c d 0 | <br>
  958. * | e f 1 | <br>
  959. * pdf multiplies matrices righthand: v' = v x m1 x m2 x ...
  960. *
  961. * @class
  962. * @name Matrix
  963. * @param {number} sx
  964. * @param {number} shy
  965. * @param {number} shx
  966. * @param {number} sy
  967. * @param {number} tx
  968. * @param {number} ty
  969. * @constructor
  970. */
  971. var Matrix = function(sx, shy, shx, sy, tx, ty) {
  972. if (!(this instanceof Matrix)) {
  973. return new Matrix(sx, shy, shx, sy, tx, ty);
  974. }
  975. if (isNaN(sx)) sx = 1;
  976. if (isNaN(shy)) shy = 0;
  977. if (isNaN(shx)) shx = 0;
  978. if (isNaN(sy)) sy = 1;
  979. if (isNaN(tx)) tx = 0;
  980. if (isNaN(ty)) ty = 0;
  981. this._matrix = [sx, shy, shx, sy, tx, ty];
  982. };
  983. /**
  984. * @name sx
  985. * @memberof Matrix#
  986. */
  987. Object.defineProperty(Matrix.prototype, "sx", {
  988. get: function() {
  989. return this._matrix[0];
  990. },
  991. set: function(value) {
  992. this._matrix[0] = value;
  993. }
  994. });
  995. /**
  996. * @name shy
  997. * @memberof Matrix#
  998. */
  999. Object.defineProperty(Matrix.prototype, "shy", {
  1000. get: function() {
  1001. return this._matrix[1];
  1002. },
  1003. set: function(value) {
  1004. this._matrix[1] = value;
  1005. }
  1006. });
  1007. /**
  1008. * @name shx
  1009. * @memberof Matrix#
  1010. */
  1011. Object.defineProperty(Matrix.prototype, "shx", {
  1012. get: function() {
  1013. return this._matrix[2];
  1014. },
  1015. set: function(value) {
  1016. this._matrix[2] = value;
  1017. }
  1018. });
  1019. /**
  1020. * @name sy
  1021. * @memberof Matrix#
  1022. */
  1023. Object.defineProperty(Matrix.prototype, "sy", {
  1024. get: function() {
  1025. return this._matrix[3];
  1026. },
  1027. set: function(value) {
  1028. this._matrix[3] = value;
  1029. }
  1030. });
  1031. /**
  1032. * @name tx
  1033. * @memberof Matrix#
  1034. */
  1035. Object.defineProperty(Matrix.prototype, "tx", {
  1036. get: function() {
  1037. return this._matrix[4];
  1038. },
  1039. set: function(value) {
  1040. this._matrix[4] = value;
  1041. }
  1042. });
  1043. /**
  1044. * @name ty
  1045. * @memberof Matrix#
  1046. */
  1047. Object.defineProperty(Matrix.prototype, "ty", {
  1048. get: function() {
  1049. return this._matrix[5];
  1050. },
  1051. set: function(value) {
  1052. this._matrix[5] = value;
  1053. }
  1054. });
  1055. Object.defineProperty(Matrix.prototype, "a", {
  1056. get: function() {
  1057. return this._matrix[0];
  1058. },
  1059. set: function(value) {
  1060. this._matrix[0] = value;
  1061. }
  1062. });
  1063. Object.defineProperty(Matrix.prototype, "b", {
  1064. get: function() {
  1065. return this._matrix[1];
  1066. },
  1067. set: function(value) {
  1068. this._matrix[1] = value;
  1069. }
  1070. });
  1071. Object.defineProperty(Matrix.prototype, "c", {
  1072. get: function() {
  1073. return this._matrix[2];
  1074. },
  1075. set: function(value) {
  1076. this._matrix[2] = value;
  1077. }
  1078. });
  1079. Object.defineProperty(Matrix.prototype, "d", {
  1080. get: function() {
  1081. return this._matrix[3];
  1082. },
  1083. set: function(value) {
  1084. this._matrix[3] = value;
  1085. }
  1086. });
  1087. Object.defineProperty(Matrix.prototype, "e", {
  1088. get: function() {
  1089. return this._matrix[4];
  1090. },
  1091. set: function(value) {
  1092. this._matrix[4] = value;
  1093. }
  1094. });
  1095. Object.defineProperty(Matrix.prototype, "f", {
  1096. get: function() {
  1097. return this._matrix[5];
  1098. },
  1099. set: function(value) {
  1100. this._matrix[5] = value;
  1101. }
  1102. });
  1103. /**
  1104. * @name rotation
  1105. * @memberof Matrix#
  1106. */
  1107. Object.defineProperty(Matrix.prototype, "rotation", {
  1108. get: function() {
  1109. return Math.atan2(this.shx, this.sx);
  1110. }
  1111. });
  1112. /**
  1113. * @name scaleX
  1114. * @memberof Matrix#
  1115. */
  1116. Object.defineProperty(Matrix.prototype, "scaleX", {
  1117. get: function() {
  1118. return this.decompose().scale.sx;
  1119. }
  1120. });
  1121. /**
  1122. * @name scaleY
  1123. * @memberof Matrix#
  1124. */
  1125. Object.defineProperty(Matrix.prototype, "scaleY", {
  1126. get: function() {
  1127. return this.decompose().scale.sy;
  1128. }
  1129. });
  1130. /**
  1131. * @name isIdentity
  1132. * @memberof Matrix#
  1133. */
  1134. Object.defineProperty(Matrix.prototype, "isIdentity", {
  1135. get: function() {
  1136. if (this.sx !== 1) {
  1137. return false;
  1138. }
  1139. if (this.shy !== 0) {
  1140. return false;
  1141. }
  1142. if (this.shx !== 0) {
  1143. return false;
  1144. }
  1145. if (this.sy !== 1) {
  1146. return false;
  1147. }
  1148. if (this.tx !== 0) {
  1149. return false;
  1150. }
  1151. if (this.ty !== 0) {
  1152. return false;
  1153. }
  1154. return true;
  1155. }
  1156. });
  1157. /**
  1158. * Join the Matrix Values to a String
  1159. *
  1160. * @function join
  1161. * @param {string} separator Specifies a string to separate each pair of adjacent elements of the array. The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If separator is an empty string, all elements are joined without any characters in between them.
  1162. * @returns {string} A string with all array elements joined.
  1163. * @memberof Matrix#
  1164. */
  1165. Matrix.prototype.join = function(separator) {
  1166. return [this.sx, this.shy, this.shx, this.sy, this.tx, this.ty]
  1167. .map(hpf)
  1168. .join(separator);
  1169. };
  1170. /**
  1171. * Multiply the matrix with given Matrix
  1172. *
  1173. * @function multiply
  1174. * @param matrix
  1175. * @returns {Matrix}
  1176. * @memberof Matrix#
  1177. */
  1178. Matrix.prototype.multiply = function(matrix) {
  1179. var sx = matrix.sx * this.sx + matrix.shy * this.shx;
  1180. var shy = matrix.sx * this.shy + matrix.shy * this.sy;
  1181. var shx = matrix.shx * this.sx + matrix.sy * this.shx;
  1182. var sy = matrix.shx * this.shy + matrix.sy * this.sy;
  1183. var tx = matrix.tx * this.sx + matrix.ty * this.shx + this.tx;
  1184. var ty = matrix.tx * this.shy + matrix.ty * this.sy + this.ty;
  1185. return new Matrix(sx, shy, shx, sy, tx, ty);
  1186. };
  1187. /**
  1188. * @function decompose
  1189. * @memberof Matrix#
  1190. */
  1191. Matrix.prototype.decompose = function() {
  1192. var a = this.sx;
  1193. var b = this.shy;
  1194. var c = this.shx;
  1195. var d = this.sy;
  1196. var e = this.tx;
  1197. var f = this.ty;
  1198. var scaleX = Math.sqrt(a * a + b * b);
  1199. a /= scaleX;
  1200. b /= scaleX;
  1201. var shear = a * c + b * d;
  1202. c -= a * shear;
  1203. d -= b * shear;
  1204. var scaleY = Math.sqrt(c * c + d * d);
  1205. c /= scaleY;
  1206. d /= scaleY;
  1207. shear /= scaleY;
  1208. if (a * d < b * c) {
  1209. a = -a;
  1210. b = -b;
  1211. shear = -shear;
  1212. scaleX = -scaleX;
  1213. }
  1214. return {
  1215. scale: new Matrix(scaleX, 0, 0, scaleY, 0, 0),
  1216. translate: new Matrix(1, 0, 0, 1, e, f),
  1217. rotate: new Matrix(a, b, -b, a, 0, 0),
  1218. skew: new Matrix(1, 0, shear, 1, 0, 0)
  1219. };
  1220. };
  1221. /**
  1222. * @function toString
  1223. * @memberof Matrix#
  1224. */
  1225. Matrix.prototype.toString = function(parmPrecision) {
  1226. return this.join(" ");
  1227. };
  1228. /**
  1229. * @function inversed
  1230. * @memberof Matrix#
  1231. */
  1232. Matrix.prototype.inversed = function() {
  1233. var a = this.sx,
  1234. b = this.shy,
  1235. c = this.shx,
  1236. d = this.sy,
  1237. e = this.tx,
  1238. f = this.ty;
  1239. var quot = 1 / (a * d - b * c);
  1240. var aInv = d * quot;
  1241. var bInv = -b * quot;
  1242. var cInv = -c * quot;
  1243. var dInv = a * quot;
  1244. var eInv = -aInv * e - cInv * f;
  1245. var fInv = -bInv * e - dInv * f;
  1246. return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv);
  1247. };
  1248. /**
  1249. * @function applyToPoint
  1250. * @memberof Matrix#
  1251. */
  1252. Matrix.prototype.applyToPoint = function(pt) {
  1253. var x = pt.x * this.sx + pt.y * this.shx + this.tx;
  1254. var y = pt.x * this.shy + pt.y * this.sy + this.ty;
  1255. return new Point(x, y);
  1256. };
  1257. /**
  1258. * @function applyToRectangle
  1259. * @memberof Matrix#
  1260. */
  1261. Matrix.prototype.applyToRectangle = function(rect) {
  1262. var pt1 = this.applyToPoint(rect);
  1263. var pt2 = this.applyToPoint(new Point(rect.x + rect.w, rect.y + rect.h));
  1264. return new Rectangle(pt1.x, pt1.y, pt2.x - pt1.x, pt2.y - pt1.y);
  1265. };
  1266. /**
  1267. * Clone the Matrix
  1268. *
  1269. * @function clone
  1270. * @memberof Matrix#
  1271. * @name clone
  1272. * @instance
  1273. */
  1274. Matrix.prototype.clone = function() {
  1275. var sx = this.sx;
  1276. var shy = this.shy;
  1277. var shx = this.shx;
  1278. var sy = this.sy;
  1279. var tx = this.tx;
  1280. var ty = this.ty;
  1281. return new Matrix(sx, shy, shx, sy, tx, ty);
  1282. };
  1283. API.Matrix = Matrix;
  1284. /**
  1285. * Multiplies two matrices. (see {@link Matrix})
  1286. * @param {Matrix} m1
  1287. * @param {Matrix} m2
  1288. * @memberof jsPDF#
  1289. * @name matrixMult
  1290. */
  1291. var matrixMult = (API.matrixMult = function(m1, m2) {
  1292. return m2.multiply(m1);
  1293. });
  1294. /**
  1295. * The identity matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)).
  1296. * @type {Matrix}
  1297. * @memberof! jsPDF#
  1298. * @name identityMatrix
  1299. */
  1300. var identityMatrix = new Matrix(1, 0, 0, 1, 0, 0);
  1301. API.unitMatrix = API.identityMatrix = identityMatrix;
  1302. /**
  1303. * Adds a new pattern for later use.
  1304. * @param {String} key The key by it can be referenced later. The keys must be unique!
  1305. * @param {API.Pattern} pattern The pattern
  1306. */
  1307. var addPattern = function(key, pattern) {
  1308. // only add it if it is not already present (the keys provided by the user must be unique!)
  1309. if (patternMap[key]) return;
  1310. var prefix = pattern instanceof ShadingPattern ? "Sh" : "P";
  1311. var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10);
  1312. pattern.id = patternKey;
  1313. patternMap[key] = patternKey;
  1314. patterns[patternKey] = pattern;
  1315. events.publish("addPattern", pattern);
  1316. };
  1317. /**
  1318. * A pattern describing a shading pattern.
  1319. *
  1320. * Only available in "advanced" API mode.
  1321. *
  1322. * @param {String} type One of "axial" or "radial"
  1323. * @param {Array<Number>} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points
  1324. * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle.
  1325. * @param {Array<Object>} colors An array of objects with the fields "offset" and "color". "offset" describes
  1326. * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255].
  1327. * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional).
  1328. * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
  1329. * and the use coordinate system (optional).
  1330. * @constructor
  1331. * @extends API.Pattern
  1332. */
  1333. API.ShadingPattern = ShadingPattern;
  1334. /**
  1335. * A PDF Tiling pattern.
  1336. *
  1337. * Only available in "advanced" API mode.
  1338. *
  1339. * @param {Array.<Number>} boundingBox The bounding box at which one pattern cell gets clipped.
  1340. * @param {Number} xStep Horizontal spacing between pattern cells.
  1341. * @param {Number} yStep Vertical spacing between pattern cells.
  1342. * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional).
  1343. * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
  1344. * and the use coordinate system (optional).
  1345. * @constructor
  1346. * @extends API.Pattern
  1347. */
  1348. API.TilingPattern = TilingPattern;
  1349. /**
  1350. * Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode.
  1351. * @param {String} key
  1352. * @param {Pattern} pattern
  1353. * @function
  1354. * @returns {jsPDF}
  1355. * @memberof jsPDF#
  1356. * @name addPattern
  1357. */
  1358. API.addShadingPattern = function(key, pattern) {
  1359. advancedApiModeTrap("addShadingPattern()");
  1360. addPattern(key, pattern);
  1361. return this;
  1362. };
  1363. /**
  1364. * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern}
  1365. * gets called. Only available in "advanced" API mode.
  1366. * @param {API.Pattern} pattern
  1367. * @memberof jsPDF#
  1368. * @name beginTilingPattern
  1369. */
  1370. API.beginTilingPattern = function(pattern) {
  1371. advancedApiModeTrap("beginTilingPattern()");
  1372. beginNewRenderTarget(
  1373. pattern.boundingBox[0],
  1374. pattern.boundingBox[1],
  1375. pattern.boundingBox[2] - pattern.boundingBox[0],
  1376. pattern.boundingBox[3] - pattern.boundingBox[1],
  1377. pattern.matrix
  1378. );
  1379. };
  1380. /**
  1381. * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called.
  1382. *
  1383. * Only available in "advanced" API mode.
  1384. *
  1385. * @param {string} key A unique key that is used to reference this pattern at later use.
  1386. * @param {API.Pattern} pattern The pattern to end.
  1387. * @memberof jsPDF#
  1388. * @name endTilingPattern
  1389. */
  1390. API.endTilingPattern = function(key, pattern) {
  1391. advancedApiModeTrap("endTilingPattern()");
  1392. // retrieve the stream
  1393. pattern.stream = pages[currentPage].join("\n");
  1394. addPattern(key, pattern);
  1395. events.publish("endTilingPattern", pattern);
  1396. // restore state from stack
  1397. renderTargetStack.pop().restore();
  1398. };
  1399. var newObject = (API.__private__.newObject = function() {
  1400. var oid = newObjectDeferred();
  1401. newObjectDeferredBegin(oid, true);
  1402. return oid;
  1403. });
  1404. // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data
  1405. var newObjectDeferred = (API.__private__.newObjectDeferred = function() {
  1406. objectNumber++;
  1407. offsets[objectNumber] = function() {
  1408. return contentLength;
  1409. };
  1410. return objectNumber;
  1411. });
  1412. var newObjectDeferredBegin = function(oid, doOutput) {
  1413. doOutput = typeof doOutput === "boolean" ? doOutput : false;
  1414. offsets[oid] = contentLength;
  1415. if (doOutput) {
  1416. out(oid + " 0 obj");
  1417. }
  1418. return oid;
  1419. };
  1420. // Does not output the object until after the pages have been output.
  1421. // Returns an object containing the objectId and content.
  1422. // All pages have been added so the object ID can be estimated to start right after.
  1423. // This does not modify the current objectNumber; It must be updated after the newObjects are output.
  1424. var newAdditionalObject = (API.__private__.newAdditionalObject = function() {
  1425. var objId = newObjectDeferred();
  1426. var obj = {
  1427. objId: objId,
  1428. content: ""
  1429. };
  1430. additionalObjects.push(obj);
  1431. return obj;
  1432. });
  1433. var rootDictionaryObjId = newObjectDeferred();
  1434. var resourceDictionaryObjId = newObjectDeferred();
  1435. /////////////////////
  1436. // Private functions
  1437. /////////////////////
  1438. var decodeColorString = (API.__private__.decodeColorString = function(color) {
  1439. var colorEncoded = color.split(" ");
  1440. if (
  1441. colorEncoded.length === 2 &&
  1442. (colorEncoded[1] === "g" || colorEncoded[1] === "G")
  1443. ) {
  1444. // convert grayscale value to rgb so that it can be converted to hex for consistency
  1445. var floatVal = parseFloat(colorEncoded[0]);
  1446. colorEncoded = [floatVal, floatVal, floatVal, "r"];
  1447. } else if (
  1448. colorEncoded.length === 5 &&
  1449. (colorEncoded[4] === "k" || colorEncoded[4] === "K")
  1450. ) {
  1451. // convert CMYK values to rbg so that it can be converted to hex for consistency
  1452. var red = (1.0 - colorEncoded[0]) * (1.0 - colorEncoded[3]);
  1453. var green = (1.0 - colorEncoded[1]) * (1.0 - colorEncoded[3]);
  1454. var blue = (1.0 - colorEncoded[2]) * (1.0 - colorEncoded[3]);
  1455. colorEncoded = [red, green, blue, "r"];
  1456. }
  1457. var colorAsRGB = "#";
  1458. for (var i = 0; i < 3; i++) {
  1459. colorAsRGB += (
  1460. "0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16)
  1461. ).slice(-2);
  1462. }
  1463. return colorAsRGB;
  1464. });
  1465. var encodeColorString = (API.__private__.encodeColorString = function(
  1466. options
  1467. ) {
  1468. var color;
  1469. if (typeof options === "string") {
  1470. options = {
  1471. ch1: options
  1472. };
  1473. }
  1474. var ch1 = options.ch1;
  1475. var ch2 = options.ch2;
  1476. var ch3 = options.ch3;
  1477. var ch4 = options.ch4;
  1478. var letterArray =
  1479. options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"];
  1480. if (typeof ch1 === "string" && ch1.charAt(0) !== "#") {
  1481. var rgbColor = new RGBColor(ch1);
  1482. if (rgbColor.ok) {
  1483. ch1 = rgbColor.toHex();
  1484. } else if (!/^\d*\.?\d*$/.test(ch1)) {
  1485. throw new Error(
  1486. 'Invalid color "' + ch1 + '" passed to jsPDF.encodeColorString.'
  1487. );
  1488. }
  1489. }
  1490. //convert short rgb to long form
  1491. if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) {
  1492. ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3];
  1493. }
  1494. if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) {
  1495. var hex = parseInt(ch1.substr(1), 16);
  1496. ch1 = (hex >> 16) & 255;
  1497. ch2 = (hex >> 8) & 255;
  1498. ch3 = hex & 255;
  1499. }
  1500. if (
  1501. typeof ch2 === "undefined" ||
  1502. (typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3)
  1503. ) {
  1504. // Gray color space.
  1505. if (typeof ch1 === "string") {
  1506. color = ch1 + " " + letterArray[0];
  1507. } else {
  1508. switch (options.precision) {
  1509. case 2:
  1510. color = f2(ch1 / 255) + " " + letterArray[0];
  1511. break;
  1512. case 3:
  1513. default:
  1514. color = f3(ch1 / 255) + " " + letterArray[0];
  1515. }
  1516. }
  1517. } else if (typeof ch4 === "undefined" || typeof ch4 === "object") {
  1518. // assume RGBA
  1519. if (ch4 && !isNaN(ch4.a)) {
  1520. //TODO Implement transparency.
  1521. //WORKAROUND use white for now, if transparent, otherwise handle as rgb
  1522. if (ch4.a === 0) {
  1523. color = ["1.", "1.", "1.", letterArray[1]].join(" ");
  1524. return color;
  1525. }
  1526. }
  1527. // assume RGB
  1528. if (typeof ch1 === "string") {
  1529. color = [ch1, ch2, ch3, letterArray[1]].join(" ");
  1530. } else {
  1531. switch (options.precision) {
  1532. case 2:
  1533. color = [
  1534. f2(ch1 / 255),
  1535. f2(ch2 / 255),
  1536. f2(ch3 / 255),
  1537. letterArray[1]
  1538. ].join(" ");
  1539. break;
  1540. default:
  1541. case 3:
  1542. color = [
  1543. f3(ch1 / 255),
  1544. f3(ch2 / 255),
  1545. f3(ch3 / 255),
  1546. letterArray[1]
  1547. ].join(" ");
  1548. }
  1549. }
  1550. } else {
  1551. // assume CMYK
  1552. if (typeof ch1 === "string") {
  1553. color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" ");
  1554. } else {
  1555. switch (options.precision) {
  1556. case 2:
  1557. color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join(
  1558. " "
  1559. );
  1560. break;
  1561. case 3:
  1562. default:
  1563. color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join(
  1564. " "
  1565. );
  1566. }
  1567. }
  1568. }
  1569. return color;
  1570. });
  1571. var getFilters = (API.__private__.getFilters = function() {
  1572. return filters;
  1573. });
  1574. var putStream = (API.__private__.putStream = function(options) {
  1575. options = options || {};
  1576. var data = options.data || "";
  1577. var filters = options.filters || getFilters();
  1578. var alreadyAppliedFilters = options.alreadyAppliedFilters || [];
  1579. var addLength1 = options.addLength1 || false;
  1580. var valueOfLength1 = data.length;
  1581. var objectId = options.objectId;
  1582. var encryptor = function(data) {
  1583. return data;
  1584. };
  1585. if (encryptionOptions !== null && typeof objectId == "undefined") {
  1586. throw new Error(
  1587. "ObjectId must be passed to putStream for file encryption"
  1588. );
  1589. }
  1590. if (encryptionOptions !== null) {
  1591. encryptor = encryption.encryptor(objectId, 0);
  1592. }
  1593. var processedData = {};
  1594. if (filters === true) {
  1595. filters = ["FlateEncode"];
  1596. }
  1597. var keyValues = options.additionalKeyValues || [];
  1598. if (typeof jsPDF.API.processDataByFilters !== "undefined") {
  1599. processedData = jsPDF.API.processDataByFilters(data, filters);
  1600. } else {
  1601. processedData = { data: data, reverseChain: [] };
  1602. }
  1603. var filterAsString =
  1604. processedData.reverseChain +
  1605. (Array.isArray(alreadyAppliedFilters)
  1606. ? alreadyAppliedFilters.join(" ")
  1607. : alreadyAppliedFilters.toString());
  1608. if (processedData.data.length !== 0) {
  1609. keyValues.push({
  1610. key: "Length",
  1611. value: processedData.data.length
  1612. });
  1613. if (addLength1 === true) {
  1614. keyValues.push({
  1615. key: "Length1",
  1616. value: valueOfLength1
  1617. });
  1618. }
  1619. }
  1620. if (filterAsString.length != 0) {
  1621. if (filterAsString.split("/").length - 1 === 1) {
  1622. keyValues.push({
  1623. key: "Filter",
  1624. value: filterAsString
  1625. });
  1626. } else {
  1627. keyValues.push({
  1628. key: "Filter",
  1629. value: "[" + filterAsString + "]"
  1630. });
  1631. for (var j = 0; j < keyValues.length; j += 1) {
  1632. if (keyValues[j].key === "DecodeParms") {
  1633. var decodeParmsArray = [];
  1634. for (
  1635. var i = 0;
  1636. i < processedData.reverseChain.split("/").length - 1;
  1637. i += 1
  1638. ) {
  1639. decodeParmsArray.push("null");
  1640. }
  1641. decodeParmsArray.push(keyValues[j].value);
  1642. keyValues[j].value = "[" + decodeParmsArray.join(" ") + "]";
  1643. }
  1644. }
  1645. }
  1646. }
  1647. out("<<");
  1648. for (var k = 0; k < keyValues.length; k++) {
  1649. out("/" + keyValues[k].key + " " + keyValues[k].value);
  1650. }
  1651. out(">>");
  1652. if (processedData.data.length !== 0) {
  1653. out("stream");
  1654. out(encryptor(processedData.data));
  1655. out("endstream");
  1656. }
  1657. });
  1658. var putPage = (API.__private__.putPage = function(page) {
  1659. var pageNumber = page.number;
  1660. var data = page.data;
  1661. var pageObjectNumber = page.objId;
  1662. var pageContentsObjId = page.contentsObjId;
  1663. newObjectDeferredBegin(pageObjectNumber, true);
  1664. out("<</Type /Page");
  1665. out("/Parent " + page.rootDictionaryObjId + " 0 R");
  1666. out("/Resources " + page.resourceDictionaryObjId + " 0 R");
  1667. out(
  1668. "/MediaBox [" +
  1669. parseFloat(hpf(page.mediaBox.bottomLeftX)) +
  1670. " " +
  1671. parseFloat(hpf(page.mediaBox.bottomLeftY)) +
  1672. " " +
  1673. hpf(page.mediaBox.topRightX) +
  1674. " " +
  1675. hpf(page.mediaBox.topRightY) +
  1676. "]"
  1677. );
  1678. if (page.cropBox !== null) {
  1679. out(
  1680. "/CropBox [" +
  1681. hpf(page.cropBox.bottomLeftX) +
  1682. " " +
  1683. hpf(page.cropBox.bottomLeftY) +
  1684. " " +
  1685. hpf(page.cropBox.topRightX) +
  1686. " " +
  1687. hpf(page.cropBox.topRightY) +
  1688. "]"
  1689. );
  1690. }
  1691. if (page.bleedBox !== null) {
  1692. out(
  1693. "/BleedBox [" +
  1694. hpf(page.bleedBox.bottomLeftX) +
  1695. " " +
  1696. hpf(page.bleedBox.bottomLeftY) +
  1697. " " +
  1698. hpf(page.bleedBox.topRightX) +
  1699. " " +
  1700. hpf(page.bleedBox.topRightY) +
  1701. "]"
  1702. );
  1703. }
  1704. if (page.trimBox !== null) {
  1705. out(
  1706. "/TrimBox [" +
  1707. hpf(page.trimBox.bottomLeftX) +
  1708. " " +
  1709. hpf(page.trimBox.bottomLeftY) +
  1710. " " +
  1711. hpf(page.trimBox.topRightX) +
  1712. " " +
  1713. hpf(page.trimBox.topRightY) +
  1714. "]"
  1715. );
  1716. }
  1717. if (page.artBox !== null) {
  1718. out(
  1719. "/ArtBox [" +
  1720. hpf(page.artBox.bottomLeftX) +
  1721. " " +
  1722. hpf(page.artBox.bottomLeftY) +
  1723. " " +
  1724. hpf(page.artBox.topRightX) +
  1725. " " +
  1726. hpf(page.artBox.topRightY) +
  1727. "]"
  1728. );
  1729. }
  1730. if (typeof page.userUnit === "number" && page.userUnit !== 1.0) {
  1731. out("/UserUnit " + page.userUnit);
  1732. }
  1733. events.publish("putPage", {
  1734. objId: pageObjectNumber,
  1735. pageContext: pagesContext[pageNumber],
  1736. pageNumber: pageNumber,
  1737. page: data
  1738. });
  1739. out("/Contents " + pageContentsObjId + " 0 R");
  1740. out(">>");
  1741. out("endobj");
  1742. // Page content
  1743. var pageContent = data.join("\n");
  1744. if (apiMode === ApiMode.ADVANCED) {
  1745. // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again
  1746. pageContent += "\nQ";
  1747. }
  1748. newObjectDeferredBegin(pageContentsObjId, true);
  1749. putStream({
  1750. data: pageContent,
  1751. filters: getFilters(),
  1752. objectId: pageContentsObjId
  1753. });
  1754. out("endobj");
  1755. return pageObjectNumber;
  1756. });
  1757. var putPages = (API.__private__.putPages = function() {
  1758. var n,
  1759. i,
  1760. pageObjectNumbers = [];
  1761. for (n = 1; n <= page; n++) {
  1762. pagesContext[n].objId = newObjectDeferred();
  1763. pagesContext[n].contentsObjId = newObjectDeferred();
  1764. }
  1765. for (n = 1; n <= page; n++) {
  1766. pageObjectNumbers.push(
  1767. putPage({
  1768. number: n,
  1769. data: pages[n],
  1770. objId: pagesContext[n].objId,
  1771. contentsObjId: pagesContext[n].contentsObjId,
  1772. mediaBox: pagesContext[n].mediaBox,
  1773. cropBox: pagesContext[n].cropBox,
  1774. bleedBox: pagesContext[n].bleedBox,
  1775. trimBox: pagesContext[n].trimBox,
  1776. artBox: pagesContext[n].artBox,
  1777. userUnit: pagesContext[n].userUnit,
  1778. rootDictionaryObjId: rootDictionaryObjId,
  1779. resourceDictionaryObjId: resourceDictionaryObjId
  1780. })
  1781. );
  1782. }
  1783. newObjectDeferredBegin(rootDictionaryObjId, true);
  1784. out("<</Type /Pages");
  1785. var kids = "/Kids [";
  1786. for (i = 0; i < page; i++) {
  1787. kids += pageObjectNumbers[i] + " 0 R ";
  1788. }
  1789. out(kids + "]");
  1790. out("/Count " + page);
  1791. out(">>");
  1792. out("endobj");
  1793. events.publish("postPutPages");
  1794. });
  1795. var putFont = function(font) {
  1796. events.publish("putFont", {
  1797. font: font,
  1798. out: out,
  1799. newObject: newObject,
  1800. putStream: putStream
  1801. });
  1802. if (font.isAlreadyPutted !== true) {
  1803. font.objectNumber = newObject();
  1804. out("<<");
  1805. out("/Type /Font");
  1806. out("/BaseFont /" + toPDFName(font.postScriptName));
  1807. out("/Subtype /Type1");
  1808. if (typeof font.encoding === "string") {
  1809. out("/Encoding /" + font.encoding);
  1810. }
  1811. out("/FirstChar 32");
  1812. out("/LastChar 255");
  1813. out(">>");
  1814. out("endobj");
  1815. }
  1816. };
  1817. var putFonts = function() {
  1818. for (var fontKey in fonts) {
  1819. if (fonts.hasOwnProperty(fontKey)) {
  1820. if (
  1821. putOnlyUsedFonts === false ||
  1822. (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey))
  1823. ) {
  1824. putFont(fonts[fontKey]);
  1825. }
  1826. }
  1827. }
  1828. };
  1829. var putXObject = function(xObject) {
  1830. xObject.objectNumber = newObject();
  1831. var options = [];
  1832. options.push({ key: "Type", value: "/XObject" });
  1833. options.push({ key: "Subtype", value: "/Form" });
  1834. options.push({
  1835. key: "BBox",
  1836. value:
  1837. "[" +
  1838. [
  1839. hpf(xObject.x),
  1840. hpf(xObject.y),
  1841. hpf(xObject.x + xObject.width),
  1842. hpf(xObject.y + xObject.height)
  1843. ].join(" ") +
  1844. "]"
  1845. });
  1846. options.push({
  1847. key: "Matrix",
  1848. value: "[" + xObject.matrix.toString() + "]"
  1849. });
  1850. // TODO: /Resources
  1851. var stream = xObject.pages[1].join("\n");
  1852. putStream({
  1853. data: stream,
  1854. additionalKeyValues: options,
  1855. objectId: xObject.objectNumber
  1856. });
  1857. out("endobj");
  1858. };
  1859. var putXObjects = function() {
  1860. for (var xObjectKey in renderTargets) {
  1861. if (renderTargets.hasOwnProperty(xObjectKey)) {
  1862. putXObject(renderTargets[xObjectKey]);
  1863. }
  1864. }
  1865. };
  1866. var interpolateAndEncodeRGBStream = function(colors, numberSamples) {
  1867. var tValues = [];
  1868. var t;
  1869. var dT = 1.0 / (numberSamples - 1);
  1870. for (t = 0.0; t < 1.0; t += dT) {
  1871. tValues.push(t);
  1872. }
  1873. tValues.push(1.0);
  1874. // add first and last control point if not present
  1875. if (colors[0].offset != 0.0) {
  1876. var c0 = {
  1877. offset: 0.0,
  1878. color: colors[0].color
  1879. };
  1880. colors.unshift(c0);
  1881. }
  1882. if (colors[colors.length - 1].offset != 1.0) {
  1883. var c1 = {
  1884. offset: 1.0,
  1885. color: colors[colors.length - 1].color
  1886. };
  1887. colors.push(c1);
  1888. }
  1889. var out = "";
  1890. var index = 0;
  1891. for (var i = 0; i < tValues.length; i++) {
  1892. t = tValues[i];
  1893. while (t > colors[index + 1].offset) index++;
  1894. var a = colors[index].offset;
  1895. var b = colors[index + 1].offset;
  1896. var d = (t - a) / (b - a);
  1897. var aColor = colors[index].color;
  1898. var bColor = colors[index + 1].color;
  1899. out +=
  1900. padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) +
  1901. padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) +
  1902. padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16));
  1903. }
  1904. return out.trim();
  1905. };
  1906. var putShadingPattern = function(pattern, numberSamples) {
  1907. /*
  1908. Axial patterns shade between the two points specified in coords, radial patterns between the inner
  1909. and outer circle.
  1910. The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now
  1911. interpolated to equidistant samples and written to pdf as a sample (type 0) function.
  1912. */
  1913. // The number of color samples that should be used to describe the shading.
  1914. // The higher, the more accurate the gradient will be.
  1915. numberSamples || (numberSamples = 21);
  1916. var funcObjectNumber = newObject();
  1917. var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples);
  1918. var options = [];
  1919. options.push({ key: "FunctionType", value: "0" });
  1920. options.push({ key: "Domain", value: "[0.0 1.0]" });
  1921. options.push({ key: "Size", value: "[" + numberSamples + "]" });
  1922. options.push({ key: "BitsPerSample", value: "8" });
  1923. options.push({ key: "Range", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" });
  1924. options.push({ key: "Decode", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" });
  1925. putStream({
  1926. data: stream,
  1927. additionalKeyValues: options,
  1928. alreadyAppliedFilters: ["/ASCIIHexDecode"],
  1929. objectId: funcObjectNumber
  1930. });
  1931. out("endobj");
  1932. pattern.objectNumber = newObject();
  1933. out("<< /ShadingType " + pattern.type);
  1934. out("/ColorSpace /DeviceRGB");
  1935. var coords =
  1936. "/Coords [" +
  1937. hpf(parseFloat(pattern.coords[0])) +
  1938. " " + // x1
  1939. hpf(parseFloat(pattern.coords[1])) +
  1940. " "; // y1
  1941. if (pattern.type === 2) {
  1942. // axial
  1943. coords +=
  1944. hpf(parseFloat(pattern.coords[2])) +
  1945. " " + // x2
  1946. hpf(parseFloat(pattern.coords[3])); // y2
  1947. } else {
  1948. // radial
  1949. coords +=
  1950. hpf(parseFloat(pattern.coords[2])) +
  1951. " " + // r1
  1952. hpf(parseFloat(pattern.coords[3])) +
  1953. " " + // x2
  1954. hpf(parseFloat(pattern.coords[4])) +
  1955. " " + // y2
  1956. hpf(parseFloat(pattern.coords[5])); // r2
  1957. }
  1958. coords += "]";
  1959. out(coords);
  1960. if (pattern.matrix) {
  1961. out("/Matrix [" + pattern.matrix.toString() + "]");
  1962. }
  1963. out("/Function " + funcObjectNumber + " 0 R");
  1964. out("/Extend [true true]");
  1965. out(">>");
  1966. out("endobj");
  1967. };
  1968. var putTilingPattern = function(pattern, deferredResourceDictionaryIds) {
  1969. var resourcesObjectId = newObjectDeferred();
  1970. var patternObjectId = newObject();
  1971. deferredResourceDictionaryIds.push({
  1972. resourcesOid: resourcesObjectId,
  1973. objectOid: patternObjectId
  1974. });
  1975. pattern.objectNumber = patternObjectId;
  1976. var options = [];
  1977. options.push({ key: "Type", value: "/Pattern" });
  1978. options.push({ key: "PatternType", value: "1" }); // tiling pattern
  1979. options.push({ key: "PaintType", value: "1" }); // colored tiling pattern
  1980. options.push({ key: "TilingType", value: "1" }); // constant spacing
  1981. options.push({
  1982. key: "BBox",
  1983. value: "[" + pattern.boundingBox.map(hpf).join(" ") + "]"
  1984. });
  1985. options.push({ key: "XStep", value: hpf(pattern.xStep) });
  1986. options.push({ key: "YStep", value: hpf(pattern.yStep) });
  1987. options.push({ key: "Resources", value: resourcesObjectId + " 0 R" });
  1988. if (pattern.matrix) {
  1989. options.push({
  1990. key: "Matrix",
  1991. value: "[" + pattern.matrix.toString() + "]"
  1992. });
  1993. }
  1994. putStream({
  1995. data: pattern.stream,
  1996. additionalKeyValues: options,
  1997. objectId: pattern.objectNumber
  1998. });
  1999. out("endobj");
  2000. };
  2001. var putPatterns = function(deferredResourceDictionaryIds) {
  2002. var patternKey;
  2003. for (patternKey in patterns) {
  2004. if (patterns.hasOwnProperty(patternKey)) {
  2005. if (patterns[patternKey] instanceof ShadingPattern) {
  2006. putShadingPattern(patterns[patternKey]);
  2007. } else if (patterns[patternKey] instanceof TilingPattern) {
  2008. putTilingPattern(patterns[patternKey], deferredResourceDictionaryIds);
  2009. }
  2010. }
  2011. }
  2012. };
  2013. var putGState = function(gState) {
  2014. gState.objectNumber = newObject();
  2015. out("<<");
  2016. for (var p in gState) {
  2017. switch (p) {
  2018. case "opacity":
  2019. out("/ca " + f2(gState[p]));
  2020. break;
  2021. case "stroke-opacity":
  2022. out("/CA " + f2(gState[p]));
  2023. break;
  2024. }
  2025. }
  2026. out(">>");
  2027. out("endobj");
  2028. };
  2029. var putGStates = function() {
  2030. var gStateKey;
  2031. for (gStateKey in gStates) {
  2032. if (gStates.hasOwnProperty(gStateKey)) {
  2033. putGState(gStates[gStateKey]);
  2034. }
  2035. }
  2036. };
  2037. var putXobjectDict = function() {
  2038. out("/XObject <<");
  2039. for (var xObjectKey in renderTargets) {
  2040. if (
  2041. renderTargets.hasOwnProperty(xObjectKey) &&
  2042. renderTargets[xObjectKey].objectNumber >= 0
  2043. ) {
  2044. out(
  2045. "/" +
  2046. xObjectKey +
  2047. " " +
  2048. renderTargets[xObjectKey].objectNumber +
  2049. " 0 R"
  2050. );
  2051. }
  2052. }
  2053. // Loop through images, or other data objects
  2054. events.publish("putXobjectDict");
  2055. out(">>");
  2056. };
  2057. var putEncryptionDict = function() {
  2058. encryption.oid = newObject();
  2059. out("<<");
  2060. out("/Filter /Standard");
  2061. out("/V " + encryption.v);
  2062. out("/R " + encryption.r);
  2063. out("/U <" + encryption.toHexString(encryption.U) + ">");
  2064. out("/O <" + encryption.toHexString(encryption.O) + ">");
  2065. out("/P " + encryption.P);
  2066. out(">>");
  2067. out("endobj");
  2068. };
  2069. var putFontDict = function() {
  2070. out("/Font <<");
  2071. for (var fontKey in fonts) {
  2072. if (fonts.hasOwnProperty(fontKey)) {
  2073. if (
  2074. putOnlyUsedFonts === false ||
  2075. (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey))
  2076. ) {
  2077. out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R");
  2078. }
  2079. }
  2080. }
  2081. out(">>");
  2082. };
  2083. var putShadingPatternDict = function() {
  2084. if (Object.keys(patterns).length > 0) {
  2085. out("/Shading <<");
  2086. for (var patternKey in patterns) {
  2087. if (
  2088. patterns.hasOwnProperty(patternKey) &&
  2089. patterns[patternKey] instanceof ShadingPattern &&
  2090. patterns[patternKey].objectNumber >= 0
  2091. ) {
  2092. out(
  2093. "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R"
  2094. );
  2095. }
  2096. }
  2097. events.publish("putShadingPatternDict");
  2098. out(">>");
  2099. }
  2100. };
  2101. var putTilingPatternDict = function(objectOid) {
  2102. if (Object.keys(patterns).length > 0) {
  2103. out("/Pattern <<");
  2104. for (var patternKey in patterns) {
  2105. if (
  2106. patterns.hasOwnProperty(patternKey) &&
  2107. patterns[patternKey] instanceof API.TilingPattern &&
  2108. patterns[patternKey].objectNumber >= 0 &&
  2109. patterns[patternKey].objectNumber < objectOid // prevent cyclic dependencies
  2110. ) {
  2111. out(
  2112. "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R"
  2113. );
  2114. }
  2115. }
  2116. events.publish("putTilingPatternDict");
  2117. out(">>");
  2118. }
  2119. };
  2120. var putGStatesDict = function() {
  2121. if (Object.keys(gStates).length > 0) {
  2122. var gStateKey;
  2123. out("/ExtGState <<");
  2124. for (gStateKey in gStates) {
  2125. if (
  2126. gStates.hasOwnProperty(gStateKey) &&
  2127. gStates[gStateKey].objectNumber >= 0
  2128. ) {
  2129. out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R");
  2130. }
  2131. }
  2132. events.publish("putGStateDict");
  2133. out(">>");
  2134. }
  2135. };
  2136. var putResourceDictionary = function(objectIds) {
  2137. newObjectDeferredBegin(objectIds.resourcesOid, true);
  2138. out("<<");
  2139. out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]");
  2140. putFontDict();
  2141. putShadingPatternDict();
  2142. putTilingPatternDict(objectIds.objectOid);
  2143. putGStatesDict();
  2144. putXobjectDict();
  2145. out(">>");
  2146. out("endobj");
  2147. };
  2148. var putResources = function() {
  2149. // FormObjects, Patterns etc. might use other FormObjects/Patterns/Images
  2150. // which means their resource dictionaries must contain the already resolved
  2151. // object ids. For this reason we defer the serialization of the resource
  2152. // dicts until all objects have been serialized and have object ids.
  2153. //
  2154. // In order to prevent cyclic dependencies (which Adobe Reader doesn't like),
  2155. // we only put all oids that are smaller than the oid of the object the
  2156. // resource dict belongs to. This is correct behavior, since the streams
  2157. // may only use other objects that have already been defined and thus appear
  2158. // earlier in their respective collection.
  2159. // Currently, this only affects tiling patterns, but a (more) correct
  2160. // implementation of FormObjects would also define their own resource dicts.
  2161. var deferredResourceDictionaryIds = [];
  2162. putFonts();
  2163. putGStates();
  2164. putXObjects();
  2165. putPatterns(deferredResourceDictionaryIds);
  2166. events.publish("putResources");
  2167. deferredResourceDictionaryIds.forEach(putResourceDictionary);
  2168. putResourceDictionary({
  2169. resourcesOid: resourceDictionaryObjId,
  2170. objectOid: Number.MAX_SAFE_INTEGER // output all objects
  2171. });
  2172. events.publish("postPutResources");
  2173. };
  2174. var putAdditionalObjects = function() {
  2175. events.publish("putAdditionalObjects");
  2176. for (var i = 0; i < additionalObjects.length; i++) {
  2177. var obj = additionalObjects[i];
  2178. newObjectDeferredBegin(obj.objId, true);
  2179. out(obj.content);
  2180. out("endobj");
  2181. }
  2182. events.publish("postPutAdditionalObjects");
  2183. };
  2184. var addFontToFontDictionary = function(font) {
  2185. fontmap[font.fontName] = fontmap[font.fontName] || {};
  2186. fontmap[font.fontName][font.fontStyle] = font.id;
  2187. };
  2188. var addFont = function(
  2189. postScriptName,
  2190. fontName,
  2191. fontStyle,
  2192. encoding,
  2193. isStandardFont
  2194. ) {
  2195. var font = {
  2196. id: "F" + (Object.keys(fonts).length + 1).toString(10),
  2197. postScriptName: postScriptName,
  2198. fontName: fontName,
  2199. fontStyle: fontStyle,
  2200. encoding: encoding,
  2201. isStandardFont: isStandardFont || false,
  2202. metadata: {}
  2203. };
  2204. events.publish("addFont", {
  2205. font: font,
  2206. instance: this
  2207. });
  2208. fonts[font.id] = font;
  2209. addFontToFontDictionary(font);
  2210. return font.id;
  2211. };
  2212. var addFonts = function(arrayOfFonts) {
  2213. for (var i = 0, l = standardFonts.length; i < l; i++) {
  2214. var fontKey = addFont.call(
  2215. this,
  2216. arrayOfFonts[i][0],
  2217. arrayOfFonts[i][1],
  2218. arrayOfFonts[i][2],
  2219. standardFonts[i][3],
  2220. true
  2221. );
  2222. if (putOnlyUsedFonts === false) {
  2223. usedFonts[fontKey] = true;
  2224. }
  2225. // adding aliases for standard fonts, this time matching the capitalization
  2226. var parts = arrayOfFonts[i][0].split("-");
  2227. addFontToFontDictionary({
  2228. id: fontKey,
  2229. fontName: parts[0],
  2230. fontStyle: parts[1] || ""
  2231. });
  2232. }
  2233. events.publish("addFonts", {
  2234. fonts: fonts,
  2235. dictionary: fontmap
  2236. });
  2237. };
  2238. var SAFE = function __safeCall(fn) {
  2239. fn.foo = function __safeCallWrapper() {
  2240. try {
  2241. return fn.apply(this, arguments);
  2242. } catch (e) {
  2243. var stack = e.stack || "";
  2244. if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1];
  2245. var m =
  2246. "Error in function " +
  2247. stack.split("\n")[0].split("<")[0] +
  2248. ": " +
  2249. e.message;
  2250. if (globalObject.console) {
  2251. globalObject.console.error(m, e);
  2252. if (globalObject.alert) alert(m);
  2253. } else {
  2254. throw new Error(m);
  2255. }
  2256. }
  2257. };
  2258. fn.foo.bar = fn;
  2259. return fn.foo;
  2260. };
  2261. var to8bitStream = function(text, flags) {
  2262. /**
  2263. * PDF 1.3 spec:
  2264. * "For text strings encoded in Unicode, the first two bytes must be 254 followed by
  2265. * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts
  2266. * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely
  2267. * to be a meaningful beginning of a word or phrase.) The remainder of the
  2268. * string consists of Unicode character codes, according to the UTF-16 encoding
  2269. * specified in the Unicode standard, version 2.0. Commonly used Unicode values
  2270. * are represented as 2 bytes per character, with the high-order byte appearing first
  2271. * in the string."
  2272. *
  2273. * In other words, if there are chars in a string with char code above 255, we
  2274. * recode the string to UCS2 BE - string doubles in length and BOM is prepended.
  2275. *
  2276. * HOWEVER!
  2277. * Actual *content* (body) text (as opposed to strings used in document properties etc)
  2278. * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID)
  2279. *
  2280. * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have
  2281. * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could
  2282. * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode
  2283. * code page. There, however, all characters in the stream are treated as GIDs,
  2284. * including BOM, which is the reason we need to skip BOM in content text (i.e. that
  2285. * that is tied to a font).
  2286. *
  2287. * To signal this "special" PDFEscape / to8bitStream handling mode,
  2288. * API.text() function sets (unless you overwrite it with manual values
  2289. * given to API.text(.., flags) )
  2290. * flags.autoencode = true
  2291. * flags.noBOM = true
  2292. *
  2293. * ===================================================================================
  2294. * `flags` properties relied upon:
  2295. * .sourceEncoding = string with encoding label.
  2296. * "Unicode" by default. = encoding of the incoming text.
  2297. * pass some non-existing encoding name
  2298. * (ex: 'Do not touch my strings! I know what I am doing.')
  2299. * to make encoding code skip the encoding step.
  2300. * .outputEncoding = Either valid PDF encoding name
  2301. * (must be supported by jsPDF font metrics, otherwise no encoding)
  2302. * or a JS object, where key = sourceCharCode, value = outputCharCode
  2303. * missing keys will be treated as: sourceCharCode === outputCharCode
  2304. * .noBOM
  2305. * See comment higher above for explanation for why this is important
  2306. * .autoencode
  2307. * See comment higher above for explanation for why this is important
  2308. */
  2309. var i,
  2310. l,
  2311. sourceEncoding,
  2312. encodingBlock,
  2313. outputEncoding,
  2314. newtext,
  2315. isUnicode,
  2316. ch,
  2317. bch;
  2318. flags = flags || {};
  2319. sourceEncoding = flags.sourceEncoding || "Unicode";
  2320. outputEncoding = flags.outputEncoding;
  2321. // This 'encoding' section relies on font metrics format
  2322. // attached to font objects by, among others,
  2323. // "Willow Systems' standard_font_metrics plugin"
  2324. // see jspdf.plugin.standard_font_metrics.js for format
  2325. // of the font.metadata.encoding Object.
  2326. // It should be something like
  2327. // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
  2328. // .widths = {0:width, code:width, ..., 'fof':divisor}
  2329. // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
  2330. if (
  2331. (flags.autoencode || outputEncoding) &&
  2332. fonts[activeFontKey].metadata &&
  2333. fonts[activeFontKey].metadata[sourceEncoding] &&
  2334. fonts[activeFontKey].metadata[sourceEncoding].encoding
  2335. ) {
  2336. encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding;
  2337. // each font has default encoding. Some have it clearly defined.
  2338. if (!outputEncoding && fonts[activeFontKey].encoding) {
  2339. outputEncoding = fonts[activeFontKey].encoding;
  2340. }
  2341. // Hmmm, the above did not work? Let's try again, in different place.
  2342. if (!outputEncoding && encodingBlock.codePages) {
  2343. outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
  2344. }
  2345. if (typeof outputEncoding === "string") {
  2346. outputEncoding = encodingBlock[outputEncoding];
  2347. }
  2348. // we want output encoding to be a JS Object, where
  2349. // key = sourceEncoding's character code and
  2350. // value = outputEncoding's character code.
  2351. if (outputEncoding) {
  2352. isUnicode = false;
  2353. newtext = [];
  2354. for (i = 0, l = text.length; i < l; i++) {
  2355. ch = outputEncoding[text.charCodeAt(i)];
  2356. if (ch) {
  2357. newtext.push(String.fromCharCode(ch));
  2358. } else {
  2359. newtext.push(text[i]);
  2360. }
  2361. // since we are looping over chars anyway, might as well
  2362. // check for residual unicodeness
  2363. if (newtext[i].charCodeAt(0) >> 8) {
  2364. /* more than 255 */
  2365. isUnicode = true;
  2366. }
  2367. }
  2368. text = newtext.join("");
  2369. }
  2370. }
  2371. i = text.length;
  2372. // isUnicode may be set to false above. Hence the triple-equal to undefined
  2373. while (isUnicode === undefined && i !== 0) {
  2374. if (text.charCodeAt(i - 1) >> 8) {
  2375. /* more than 255 */
  2376. isUnicode = true;
  2377. }
  2378. i--;
  2379. }
  2380. if (!isUnicode) {
  2381. return text;
  2382. }
  2383. newtext = flags.noBOM ? [] : [254, 255];
  2384. for (i = 0, l = text.length; i < l; i++) {
  2385. ch = text.charCodeAt(i);
  2386. bch = ch >> 8; // divide by 256
  2387. if (bch >> 8) {
  2388. /* something left after dividing by 256 second time */
  2389. throw new Error(
  2390. "Character at position " +
  2391. i +
  2392. " of string '" +
  2393. text +
  2394. "' exceeds 16bits. Cannot be encoded into UCS-2 BE"
  2395. );
  2396. }
  2397. newtext.push(bch);
  2398. newtext.push(ch - (bch << 8));
  2399. }
  2400. return String.fromCharCode.apply(undefined, newtext);
  2401. };
  2402. var pdfEscape = (API.__private__.pdfEscape = API.pdfEscape = function(
  2403. text,
  2404. flags
  2405. ) {
  2406. /**
  2407. * Replace '/', '(', and ')' with pdf-safe versions
  2408. *
  2409. * Doing to8bitStream does NOT make this PDF display unicode text. For that
  2410. * we also need to reference a unicode font and embed it - royal pain in the rear.
  2411. *
  2412. * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
  2413. * which JavaScript Strings are happy to provide. So, while we still cannot display
  2414. * 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
  2415. * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
  2416. * is still parseable.
  2417. * This will allow immediate support for unicode in document properties strings.
  2418. */
  2419. return to8bitStream(text, flags)
  2420. .replace(/\\/g, "\\\\")
  2421. .replace(/\(/g, "\\(")
  2422. .replace(/\)/g, "\\)");
  2423. });
  2424. var beginPage = (API.__private__.beginPage = function(format) {
  2425. pages[++page] = [];
  2426. pagesContext[page] = {
  2427. objId: 0,
  2428. contentsObjId: 0,
  2429. userUnit: Number(userUnit),
  2430. artBox: null,
  2431. bleedBox: null,
  2432. cropBox: null,
  2433. trimBox: null,
  2434. mediaBox: {
  2435. bottomLeftX: 0,
  2436. bottomLeftY: 0,
  2437. topRightX: Number(format[0]),
  2438. topRightY: Number(format[1])
  2439. }
  2440. };
  2441. _setPage(page);
  2442. setOutputDestination(pages[currentPage]);
  2443. });
  2444. var _addPage = function(parmFormat, parmOrientation) {
  2445. var dimensions, width, height;
  2446. orientation = parmOrientation || orientation;
  2447. if (typeof parmFormat === "string") {
  2448. dimensions = getPageFormat(parmFormat.toLowerCase());
  2449. if (Array.isArray(dimensions)) {
  2450. width = dimensions[0];
  2451. height = dimensions[1];
  2452. }
  2453. }
  2454. if (Array.isArray(parmFormat)) {
  2455. width = parmFormat[0] * scaleFactor;
  2456. height = parmFormat[1] * scaleFactor;
  2457. }
  2458. if (isNaN(width)) {
  2459. width = format[0];
  2460. height = format[1];
  2461. }
  2462. if (width > 14400 || height > 14400) {
  2463. console.warn(
  2464. "A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"
  2465. );
  2466. width = Math.min(14400, width);
  2467. height = Math.min(14400, height);
  2468. }
  2469. format = [width, height];
  2470. switch (orientation.substr(0, 1)) {
  2471. case "l":
  2472. if (height > width) {
  2473. format = [height, width];
  2474. }
  2475. break;
  2476. case "p":
  2477. if (width > height) {
  2478. format = [height, width];
  2479. }
  2480. break;
  2481. }
  2482. beginPage(format);
  2483. // Set line width
  2484. setLineWidth(lineWidth);
  2485. // Set draw color
  2486. out(strokeColor);
  2487. // resurrecting non-default line caps, joins
  2488. if (lineCapID !== 0) {
  2489. out(lineCapID + " J");
  2490. }
  2491. if (lineJoinID !== 0) {
  2492. out(lineJoinID + " j");
  2493. }
  2494. events.publish("addPage", {
  2495. pageNumber: page
  2496. });
  2497. };
  2498. var _deletePage = function(n) {
  2499. if (n > 0 && n <= page) {
  2500. pages.splice(n, 1);
  2501. pagesContext.splice(n, 1);
  2502. page--;
  2503. if (currentPage > page) {
  2504. currentPage = page;
  2505. }
  2506. this.setPage(currentPage);
  2507. }
  2508. };
  2509. var _setPage = function(n) {
  2510. if (n > 0 && n <= page) {
  2511. currentPage = n;
  2512. }
  2513. };
  2514. var getNumberOfPages = (API.__private__.getNumberOfPages = API.getNumberOfPages = function() {
  2515. return pages.length - 1;
  2516. });
  2517. /**
  2518. * Returns a document-specific font key - a label assigned to a
  2519. * font name + font type combination at the time the font was added
  2520. * to the font inventory.
  2521. *
  2522. * Font key is used as label for the desired font for a block of text
  2523. * to be added to the PDF document stream.
  2524. * @private
  2525. * @function
  2526. * @param fontName {string} can be undefined on "falthy" to indicate "use current"
  2527. * @param fontStyle {string} can be undefined on "falthy" to indicate "use current"
  2528. * @returns {string} Font key.
  2529. * @ignore
  2530. */
  2531. var getFont = function(fontName, fontStyle, options) {
  2532. var key = undefined,
  2533. fontNameLowerCase;
  2534. options = options || {};
  2535. fontName =
  2536. fontName !== undefined ? fontName : fonts[activeFontKey].fontName;
  2537. fontStyle =
  2538. fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle;
  2539. fontNameLowerCase = fontName.toLowerCase();
  2540. if (
  2541. fontmap[fontNameLowerCase] !== undefined &&
  2542. fontmap[fontNameLowerCase][fontStyle] !== undefined
  2543. ) {
  2544. key = fontmap[fontNameLowerCase][fontStyle];
  2545. } else if (
  2546. fontmap[fontName] !== undefined &&
  2547. fontmap[fontName][fontStyle] !== undefined
  2548. ) {
  2549. key = fontmap[fontName][fontStyle];
  2550. } else {
  2551. if (options.disableWarning === false) {
  2552. console.warn(
  2553. "Unable to look up font label for font '" +
  2554. fontName +
  2555. "', '" +
  2556. fontStyle +
  2557. "'. Refer to getFontList() for available fonts."
  2558. );
  2559. }
  2560. }
  2561. if (!key && !options.noFallback) {
  2562. key = fontmap["times"][fontStyle];
  2563. if (key == null) {
  2564. key = fontmap["times"]["normal"];
  2565. }
  2566. }
  2567. return key;
  2568. };
  2569. var putInfo = (API.__private__.putInfo = function() {
  2570. var objectId = newObject();
  2571. var encryptor = function(data) {
  2572. return data;
  2573. };
  2574. if (encryptionOptions !== null) {
  2575. encryptor = encryption.encryptor(objectId, 0);
  2576. }
  2577. out("<<");
  2578. out("/Producer (" + pdfEscape(encryptor("jsPDF " + jsPDF.version)) + ")");
  2579. for (var key in documentProperties) {
  2580. if (documentProperties.hasOwnProperty(key) && documentProperties[key]) {
  2581. out(
  2582. "/" +
  2583. key.substr(0, 1).toUpperCase() +
  2584. key.substr(1) +
  2585. " (" +
  2586. pdfEscape(encryptor(documentProperties[key])) +
  2587. ")"
  2588. );
  2589. }
  2590. }
  2591. out("/CreationDate (" + pdfEscape(encryptor(creationDate)) + ")");
  2592. out(">>");
  2593. out("endobj");
  2594. });
  2595. var putCatalog = (API.__private__.putCatalog = function(options) {
  2596. options = options || {};
  2597. var tmpRootDictionaryObjId =
  2598. options.rootDictionaryObjId || rootDictionaryObjId;
  2599. newObject();
  2600. out("<<");
  2601. out("/Type /Catalog");
  2602. out("/Pages " + tmpRootDictionaryObjId + " 0 R");
  2603. // PDF13ref Section 7.2.1
  2604. if (!zoomMode) zoomMode = "fullwidth";
  2605. switch (zoomMode) {
  2606. case "fullwidth":
  2607. out("/OpenAction [3 0 R /FitH null]");
  2608. break;
  2609. case "fullheight":
  2610. out("/OpenAction [3 0 R /FitV null]");
  2611. break;
  2612. case "fullpage":
  2613. out("/OpenAction [3 0 R /Fit]");
  2614. break;
  2615. case "original":
  2616. out("/OpenAction [3 0 R /XYZ null null 1]");
  2617. break;
  2618. default:
  2619. var pcn = "" + zoomMode;
  2620. if (pcn.substr(pcn.length - 1) === "%")
  2621. zoomMode = parseInt(zoomMode) / 100;
  2622. if (typeof zoomMode === "number") {
  2623. out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]");
  2624. }
  2625. }
  2626. if (!layoutMode) layoutMode = "continuous";
  2627. switch (layoutMode) {
  2628. case "continuous":
  2629. out("/PageLayout /OneColumn");
  2630. break;
  2631. case "single":
  2632. out("/PageLayout /SinglePage");
  2633. break;
  2634. case "two":
  2635. case "twoleft":
  2636. out("/PageLayout /TwoColumnLeft");
  2637. break;
  2638. case "tworight":
  2639. out("/PageLayout /TwoColumnRight");
  2640. break;
  2641. }
  2642. if (pageMode) {
  2643. /**
  2644. * A name object specifying how the document should be displayed when opened:
  2645. * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT
  2646. * UseOutlines : Document outline visible
  2647. * UseThumbs : Thumbnail images visible
  2648. * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible
  2649. */
  2650. out("/PageMode /" + pageMode);
  2651. }
  2652. events.publish("putCatalog");
  2653. out(">>");
  2654. out("endobj");
  2655. });
  2656. var putTrailer = (API.__private__.putTrailer = function() {
  2657. out("trailer");
  2658. out("<<");
  2659. out("/Size " + (objectNumber + 1));
  2660. // Root and Info must be the last and second last objects written respectively
  2661. out("/Root " + objectNumber + " 0 R");
  2662. out("/Info " + (objectNumber - 1) + " 0 R");
  2663. if (encryptionOptions !== null) {
  2664. out("/Encrypt " + encryption.oid + " 0 R");
  2665. }
  2666. out("/ID [ <" + fileId + "> <" + fileId + "> ]");
  2667. out(">>");
  2668. });
  2669. var putHeader = (API.__private__.putHeader = function() {
  2670. out("%PDF-" + pdfVersion);
  2671. out("%\xBA\xDF\xAC\xE0");
  2672. });
  2673. var putXRef = (API.__private__.putXRef = function() {
  2674. var p = "0000000000";
  2675. out("xref");
  2676. out("0 " + (objectNumber + 1));
  2677. out("0000000000 65535 f ");
  2678. for (var i = 1; i <= objectNumber; i++) {
  2679. var offset = offsets[i];
  2680. if (typeof offset === "function") {
  2681. out((p + offsets[i]()).slice(-10) + " 00000 n ");
  2682. } else {
  2683. if (typeof offsets[i] !== "undefined") {
  2684. out((p + offsets[i]).slice(-10) + " 00000 n ");
  2685. } else {
  2686. out("0000000000 00000 n ");
  2687. }
  2688. }
  2689. }
  2690. });
  2691. var buildDocument = (API.__private__.buildDocument = function() {
  2692. resetDocument();
  2693. setOutputDestination(content);
  2694. events.publish("buildDocument");
  2695. putHeader();
  2696. putPages();
  2697. putAdditionalObjects();
  2698. putResources();
  2699. if (encryptionOptions !== null) putEncryptionDict();
  2700. putInfo();
  2701. putCatalog();
  2702. var offsetOfXRef = contentLength;
  2703. putXRef();
  2704. putTrailer();
  2705. out("startxref");
  2706. out("" + offsetOfXRef);
  2707. out("%%EOF");
  2708. setOutputDestination(pages[currentPage]);
  2709. return content.join("\n");
  2710. });
  2711. var getBlob = (API.__private__.getBlob = function(data) {
  2712. return new Blob([getArrayBuffer(data)], {
  2713. type: "application/pdf"
  2714. });
  2715. });
  2716. /**
  2717. * Generates the PDF document.
  2718. *
  2719. * If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
  2720. *
  2721. * @param {string} type A string identifying one of the possible output types.<br/>
  2722. * Possible values are: <br/>
  2723. * 'arraybuffer' -> (ArrayBuffer)<br/>
  2724. * 'blob' -> (Blob)<br/>
  2725. * 'bloburi'/'bloburl' -> (string)<br/>
  2726. * 'datauristring'/'dataurlstring' -> (string)<br/>
  2727. * 'datauri'/'dataurl' -> (undefined) -> change location to generated datauristring/dataurlstring<br/>
  2728. * 'dataurlnewwindow' -> (window | null | undefined) throws error if global isn't a window object(node)<br/>
  2729. * 'pdfobjectnewwindow' -> (window | null) throws error if global isn't a window object(node)<br/>
  2730. * 'pdfjsnewwindow' -> (wind | null)
  2731. * @param {Object|string} options An object providing some additional signalling to PDF generator.<br/>
  2732. * Possible options are 'filename'.<br/>
  2733. * A string can be passed instead of {filename:string} and defaults to 'generated.pdf'
  2734. * @function
  2735. * @instance
  2736. * @returns {string|window|ArrayBuffer|Blob|jsPDF|null|undefined}
  2737. * @memberof jsPDF#
  2738. * @name output
  2739. */
  2740. var output = (API.output = API.__private__.output = SAFE(function output(
  2741. type,
  2742. options
  2743. ) {
  2744. options = options || {};
  2745. if (typeof options === "string") {
  2746. options = {
  2747. filename: options
  2748. };
  2749. } else {
  2750. options.filename = options.filename || "generated.pdf";
  2751. }
  2752. switch (type) {
  2753. case undefined:
  2754. return buildDocument();
  2755. case "save":
  2756. API.save(options.filename);
  2757. break;
  2758. case "arraybuffer":
  2759. return getArrayBuffer(buildDocument());
  2760. case "blob":
  2761. return getBlob(buildDocument());
  2762. case "bloburi":
  2763. case "bloburl":
  2764. // Developer is responsible of calling revokeObjectURL
  2765. if (
  2766. typeof globalObject.URL !== "undefined" &&
  2767. typeof globalObject.URL.createObjectURL === "function"
  2768. ) {
  2769. return (
  2770. (globalObject.URL &&
  2771. globalObject.URL.createObjectURL(getBlob(buildDocument()))) ||
  2772. void 0
  2773. );
  2774. } else {
  2775. console.warn(
  2776. "bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser."
  2777. );
  2778. }
  2779. break;
  2780. case "datauristring":
  2781. case "dataurlstring":
  2782. var dataURI = "";
  2783. var pdfDocument = buildDocument();
  2784. try {
  2785. dataURI = btoa(pdfDocument);
  2786. } catch (e) {
  2787. dataURI = btoa(unescape(encodeURIComponent(pdfDocument)));
  2788. }
  2789. return (
  2790. "data:application/pdf;filename=" +
  2791. options.filename +
  2792. ";base64," +
  2793. dataURI
  2794. );
  2795. case "pdfobjectnewwindow":
  2796. if (
  2797. Object.prototype.toString.call(globalObject) === "[object Window]"
  2798. ) {
  2799. var pdfObjectUrl =
  2800. "https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js";
  2801. var integrity =
  2802. ' integrity="sha512-4ze/a9/4jqu+tX9dfOqJYSvyYd5M6qum/3HpCLr+/Jqf0whc37VUbkpNGHR7/8pSnCFw47T1fmIpwBV7UySh3g==" crossorigin="anonymous"';
  2803. if (options.pdfObjectUrl) {
  2804. pdfObjectUrl = options.pdfObjectUrl;
  2805. integrity = "";
  2806. }
  2807. var htmlForNewWindow =
  2808. "<html>" +
  2809. '<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><script src="' +
  2810. pdfObjectUrl +
  2811. '"' +
  2812. integrity +
  2813. '></script><script >PDFObject.embed("' +
  2814. this.output("dataurlstring") +
  2815. '", ' +
  2816. JSON.stringify(options) +
  2817. ");</script></body></html>";
  2818. var nW = globalObject.open();
  2819. if (nW !== null) {
  2820. nW.document.write(htmlForNewWindow);
  2821. }
  2822. return nW;
  2823. } else {
  2824. throw new Error(
  2825. "The option pdfobjectnewwindow just works in a browser-environment."
  2826. );
  2827. }
  2828. case "pdfjsnewwindow":
  2829. if (
  2830. Object.prototype.toString.call(globalObject) === "[object Window]"
  2831. ) {
  2832. var pdfJsUrl = options.pdfJsUrl || "examples/PDF.js/web/viewer.html";
  2833. var htmlForPDFjsNewWindow =
  2834. "<html>" +
  2835. "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style>" +
  2836. '<body><iframe id="pdfViewer" src="' +
  2837. pdfJsUrl +
  2838. "?file=&downloadName=" +
  2839. options.filename +
  2840. '" width="500px" height="400px" />' +
  2841. "</body></html>";
  2842. var PDFjsNewWindow = globalObject.open();
  2843. if (PDFjsNewWindow !== null) {
  2844. PDFjsNewWindow.document.write(htmlForPDFjsNewWindow);
  2845. var scope = this;
  2846. PDFjsNewWindow.document.documentElement.querySelector(
  2847. "#pdfViewer"
  2848. ).onload = function() {
  2849. PDFjsNewWindow.document.title = options.filename;
  2850. PDFjsNewWindow.document.documentElement
  2851. .querySelector("#pdfViewer")
  2852. .contentWindow.PDFViewerApplication.open(
  2853. scope.output("bloburl")
  2854. );
  2855. };
  2856. }
  2857. return PDFjsNewWindow;
  2858. } else {
  2859. throw new Error(
  2860. "The option pdfjsnewwindow just works in a browser-environment."
  2861. );
  2862. }
  2863. case "dataurlnewwindow":
  2864. if (
  2865. Object.prototype.toString.call(globalObject) === "[object Window]"
  2866. ) {
  2867. var htmlForDataURLNewWindow =
  2868. "<html>" +
  2869. "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style>" +
  2870. "<body>" +
  2871. '<iframe src="' +
  2872. this.output("datauristring", options) +
  2873. '"></iframe>' +
  2874. "</body></html>";
  2875. var dataURLNewWindow = globalObject.open();
  2876. if (dataURLNewWindow !== null) {
  2877. dataURLNewWindow.document.write(htmlForDataURLNewWindow);
  2878. dataURLNewWindow.document.title = options.filename;
  2879. }
  2880. if (dataURLNewWindow || typeof safari === "undefined")
  2881. return dataURLNewWindow;
  2882. } else {
  2883. throw new Error(
  2884. "The option dataurlnewwindow just works in a browser-environment."
  2885. );
  2886. }
  2887. break;
  2888. case "datauri":
  2889. case "dataurl":
  2890. return (globalObject.document.location.href = this.output(
  2891. "datauristring",
  2892. options
  2893. ));
  2894. default:
  2895. return null;
  2896. }
  2897. }));
  2898. /**
  2899. * Used to see if a supplied hotfix was requested when the pdf instance was created.
  2900. * @param {string} hotfixName - The name of the hotfix to check.
  2901. * @returns {boolean}
  2902. */
  2903. var hasHotfix = function(hotfixName) {
  2904. return (
  2905. Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1
  2906. );
  2907. };
  2908. switch (unit) {
  2909. case "pt":
  2910. scaleFactor = 1;
  2911. break;
  2912. case "mm":
  2913. scaleFactor = 72 / 25.4;
  2914. break;
  2915. case "cm":
  2916. scaleFactor = 72 / 2.54;
  2917. break;
  2918. case "in":
  2919. scaleFactor = 72;
  2920. break;
  2921. case "px":
  2922. if (hasHotfix("px_scaling") == true) {
  2923. scaleFactor = 72 / 96;
  2924. } else {
  2925. scaleFactor = 96 / 72;
  2926. }
  2927. break;
  2928. case "pc":
  2929. scaleFactor = 12;
  2930. break;
  2931. case "em":
  2932. scaleFactor = 12;
  2933. break;
  2934. case "ex":
  2935. scaleFactor = 6;
  2936. break;
  2937. default:
  2938. if (typeof unit === "number") {
  2939. scaleFactor = unit;
  2940. } else {
  2941. throw new Error("Invalid unit: " + unit);
  2942. }
  2943. }
  2944. var encryption = null;
  2945. setCreationDate();
  2946. setFileId();
  2947. var getEncryptor = function(objectId) {
  2948. if (encryptionOptions !== null) {
  2949. return encryption.encryptor(objectId, 0);
  2950. }
  2951. return function(data) {
  2952. return data;
  2953. };
  2954. };
  2955. //---------------------------------------
  2956. // Public API
  2957. var getPageInfo = (API.__private__.getPageInfo = API.getPageInfo = function(
  2958. pageNumberOneBased
  2959. ) {
  2960. if (isNaN(pageNumberOneBased) || pageNumberOneBased % 1 !== 0) {
  2961. throw new Error("Invalid argument passed to jsPDF.getPageInfo");
  2962. }
  2963. var objId = pagesContext[pageNumberOneBased].objId;
  2964. return {
  2965. objId: objId,
  2966. pageNumber: pageNumberOneBased,
  2967. pageContext: pagesContext[pageNumberOneBased]
  2968. };
  2969. });
  2970. var getPageInfoByObjId = (API.__private__.getPageInfoByObjId = function(
  2971. objId
  2972. ) {
  2973. if (isNaN(objId) || objId % 1 !== 0) {
  2974. throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId");
  2975. }
  2976. for (var pageNumber in pagesContext) {
  2977. if (pagesContext[pageNumber].objId === objId) {
  2978. break;
  2979. }
  2980. }
  2981. return getPageInfo(pageNumber);
  2982. });
  2983. var getCurrentPageInfo = (API.__private__.getCurrentPageInfo = API.getCurrentPageInfo = function() {
  2984. return {
  2985. objId: pagesContext[currentPage].objId,
  2986. pageNumber: currentPage,
  2987. pageContext: pagesContext[currentPage]
  2988. };
  2989. });
  2990. /**
  2991. * Adds (and transfers the focus to) new page to the PDF document.
  2992. * @param format {String/Array} The format of the new page. Can be: <ul><li>a0 - a10</li><li>b0 - b10</li><li>c0 - c10</li><li>dl</li><li>letter</li><li>government-letter</li><li>legal</li><li>junior-legal</li><li>ledger</li><li>tabloid</li><li>credit-card</li></ul><br />
  2993. * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89]
  2994. * @param orientation {string} Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l").
  2995. * @function
  2996. * @instance
  2997. * @returns {jsPDF}
  2998. *
  2999. * @memberof jsPDF#
  3000. * @name addPage
  3001. */
  3002. API.addPage = function() {
  3003. _addPage.apply(this, arguments);
  3004. return this;
  3005. };
  3006. /**
  3007. * Adds (and transfers the focus to) new page to the PDF document.
  3008. * @function
  3009. * @instance
  3010. * @returns {jsPDF}
  3011. *
  3012. * @memberof jsPDF#
  3013. * @name setPage
  3014. * @param {number} page Switch the active page to the page number specified (indexed starting at 1).
  3015. * @example
  3016. * doc = jsPDF()
  3017. * doc.addPage()
  3018. * doc.addPage()
  3019. * doc.text('I am on page 3', 10, 10)
  3020. * doc.setPage(1)
  3021. * doc.text('I am on page 1', 10, 10)
  3022. */
  3023. API.setPage = function() {
  3024. _setPage.apply(this, arguments);
  3025. setOutputDestination.call(this, pages[currentPage]);
  3026. return this;
  3027. };
  3028. /**
  3029. * @name insertPage
  3030. * @memberof jsPDF#
  3031. *
  3032. * @function
  3033. * @instance
  3034. * @param {Object} beforePage
  3035. * @returns {jsPDF}
  3036. */
  3037. API.insertPage = function(beforePage) {
  3038. this.addPage();
  3039. this.movePage(currentPage, beforePage);
  3040. return this;
  3041. };
  3042. /**
  3043. * @name movePage
  3044. * @memberof jsPDF#
  3045. * @function
  3046. * @instance
  3047. * @param {number} targetPage
  3048. * @param {number} beforePage
  3049. * @returns {jsPDF}
  3050. */
  3051. API.movePage = function(targetPage, beforePage) {
  3052. var tmpPages, tmpPagesContext;
  3053. if (targetPage > beforePage) {
  3054. tmpPages = pages[targetPage];
  3055. tmpPagesContext = pagesContext[targetPage];
  3056. for (var i = targetPage; i > beforePage; i--) {
  3057. pages[i] = pages[i - 1];
  3058. pagesContext[i] = pagesContext[i - 1];
  3059. }
  3060. pages[beforePage] = tmpPages;
  3061. pagesContext[beforePage] = tmpPagesContext;
  3062. this.setPage(beforePage);
  3063. } else if (targetPage < beforePage) {
  3064. tmpPages = pages[targetPage];
  3065. tmpPagesContext = pagesContext[targetPage];
  3066. for (var j = targetPage; j < beforePage; j++) {
  3067. pages[j] = pages[j + 1];
  3068. pagesContext[j] = pagesContext[j + 1];
  3069. }
  3070. pages[beforePage] = tmpPages;
  3071. pagesContext[beforePage] = tmpPagesContext;
  3072. this.setPage(beforePage);
  3073. }
  3074. return this;
  3075. };
  3076. /**
  3077. * Deletes a page from the PDF.
  3078. * @name deletePage
  3079. * @memberof jsPDF#
  3080. * @function
  3081. * @param {number} targetPage
  3082. * @instance
  3083. * @returns {jsPDF}
  3084. */
  3085. API.deletePage = function() {
  3086. _deletePage.apply(this, arguments);
  3087. return this;
  3088. };
  3089. /**
  3090. * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings.
  3091. *
  3092. * @function
  3093. * @instance
  3094. * @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.
  3095. * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page.
  3096. * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page.
  3097. * @param {Object} [options] - Collection of settings signaling how the text must be encoded.
  3098. * @param {string} [options.align=left] - The alignment of the text, possible values: left, center, right, justify.
  3099. * @param {string} [options.baseline=alphabetic] - Sets text baseline used when drawing the text, possible values: alphabetic, ideographic, bottom, top, middle, hanging
  3100. * @param {number|Matrix} [options.angle=0] - Rotate the text clockwise or counterclockwise. Expects the angle in degree.
  3101. * @param {number} [options.rotationDirection=1] - Direction of the rotation. 0 = clockwise, 1 = counterclockwise.
  3102. * @param {number} [options.charSpace=0] - The space between each letter.
  3103. * @param {number} [options.horizontalScale=1] - Horizontal scale of the text as a factor of the regular size.
  3104. * @param {number} [options.lineHeightFactor=1.15] - The lineheight of each line.
  3105. * @param {Object} [options.flags] - Flags for to8bitStream.
  3106. * @param {boolean} [options.flags.noBOM=true] - Don't add BOM to Unicode-text.
  3107. * @param {boolean} [options.flags.autoencode=true] - Autoencode the Text.
  3108. * @param {number} [options.maxWidth=0] - Split the text by given width, 0 = no split.
  3109. * @param {string} [options.renderingMode=fill] - Set how the text should be rendered, possible values: fill, stroke, fillThenStroke, invisible, fillAndAddForClipping, strokeAndAddPathForClipping, fillThenStrokeAndAddToPathForClipping, addToPathForClipping.
  3110. * @param {boolean} [options.isInputVisual] - Option for the BidiEngine
  3111. * @param {boolean} [options.isOutputVisual] - Option for the BidiEngine
  3112. * @param {boolean} [options.isInputRtl] - Option for the BidiEngine
  3113. * @param {boolean} [options.isOutputRtl] - Option for the BidiEngine
  3114. * @param {boolean} [options.isSymmetricSwapping] - Option for the BidiEngine
  3115. * @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the anchor set by x and y.
  3116. *
  3117. * If it is a Matrix, this matrix gets directly applied to the text, which allows shearing
  3118. * effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this
  3119. * matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a
  3120. * DIFFERENT result. A matrix is only allowed in "advanced" API mode.
  3121. * @returns {jsPDF}
  3122. * @memberof jsPDF#
  3123. * @name text
  3124. */
  3125. API.__private__.text = API.text = function(text, x, y, options, transform) {
  3126. /*
  3127. * Inserts something like this into PDF
  3128. * BT
  3129. * /F1 16 Tf % Font name + size
  3130. * 16 TL % How many units down for next line in multiline text
  3131. * 0 g % color
  3132. * 28.35 813.54 Td % position
  3133. * (line one) Tj
  3134. * T* (line two) Tj
  3135. * T* (line three) Tj
  3136. * ET
  3137. */
  3138. options = options || {};
  3139. var scope = options.scope || this;
  3140. var payload, da, angle, align, charSpace, maxWidth, flags, horizontalScale;
  3141. // Pre-August-2012 the order of arguments was function(x, y, text, flags)
  3142. // in effort to make all calls have similar signature like
  3143. // function(data, coordinates... , miscellaneous)
  3144. // this method had its args flipped.
  3145. // code below allows backward compatibility with old arg order.
  3146. if (
  3147. typeof text === "number" &&
  3148. typeof x === "number" &&
  3149. (typeof y === "string" || Array.isArray(y))
  3150. ) {
  3151. var tmp = y;
  3152. y = x;
  3153. x = text;
  3154. text = tmp;
  3155. }
  3156. var transformationMatrix;
  3157. if (arguments[3] instanceof Matrix === false) {
  3158. flags = arguments[3];
  3159. angle = arguments[4];
  3160. align = arguments[5];
  3161. if (typeof flags !== "object" || flags === null) {
  3162. if (typeof angle === "string") {
  3163. align = angle;
  3164. angle = null;
  3165. }
  3166. if (typeof flags === "string") {
  3167. align = flags;
  3168. flags = null;
  3169. }
  3170. if (typeof flags === "number") {
  3171. angle = flags;
  3172. flags = null;
  3173. }
  3174. options = {
  3175. flags: flags,
  3176. angle: angle,
  3177. align: align
  3178. };
  3179. }
  3180. } else {
  3181. advancedApiModeTrap(
  3182. "The transform parameter of text() with a Matrix value"
  3183. );
  3184. transformationMatrix = transform;
  3185. }
  3186. if (isNaN(x) || isNaN(y) || typeof text === "undefined" || text === null) {
  3187. throw new Error("Invalid arguments passed to jsPDF.text");
  3188. }
  3189. if (text.length === 0) {
  3190. return scope;
  3191. }
  3192. var xtra = "";
  3193. var isHex = false;
  3194. var lineHeight =
  3195. typeof options.lineHeightFactor === "number"
  3196. ? options.lineHeightFactor
  3197. : lineHeightFactor;
  3198. var scaleFactor = scope.internal.scaleFactor;
  3199. function ESC(s) {
  3200. s = s.split("\t").join(Array(options.TabLen || 9).join(" "));
  3201. return pdfEscape(s, flags);
  3202. }
  3203. function transformTextToSpecialArray(text) {
  3204. //we don't want to destroy original text array, so cloning it
  3205. var sa = text.concat();
  3206. var da = [];
  3207. var len = sa.length;
  3208. var curDa;
  3209. //we do array.join('text that must not be PDFescaped")
  3210. //thus, pdfEscape each component separately
  3211. while (len--) {
  3212. curDa = sa.shift();
  3213. if (typeof curDa === "string") {
  3214. da.push(curDa);
  3215. } else {
  3216. if (
  3217. Array.isArray(text) &&
  3218. (curDa.length === 1 ||
  3219. (curDa[1] === undefined && curDa[2] === undefined))
  3220. ) {
  3221. da.push(curDa[0]);
  3222. } else {
  3223. da.push([curDa[0], curDa[1], curDa[2]]);
  3224. }
  3225. }
  3226. }
  3227. return da;
  3228. }
  3229. function processTextByFunction(text, processingFunction) {
  3230. var result;
  3231. if (typeof text === "string") {
  3232. result = processingFunction(text)[0];
  3233. } else if (Array.isArray(text)) {
  3234. //we don't want to destroy original text array, so cloning it
  3235. var sa = text.concat();
  3236. var da = [];
  3237. var len = sa.length;
  3238. var curDa;
  3239. var tmpResult;
  3240. //we do array.join('text that must not be PDFescaped")
  3241. //thus, pdfEscape each component separately
  3242. while (len--) {
  3243. curDa = sa.shift();
  3244. if (typeof curDa === "string") {
  3245. da.push(processingFunction(curDa)[0]);
  3246. } else if (Array.isArray(curDa) && typeof curDa[0] === "string") {
  3247. tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]);
  3248. da.push([tmpResult[0], tmpResult[1], tmpResult[2]]);
  3249. }
  3250. }
  3251. result = da;
  3252. }
  3253. return result;
  3254. }
  3255. //Check if text is of type String
  3256. var textIsOfTypeString = false;
  3257. var tmpTextIsOfTypeString = true;
  3258. if (typeof text === "string") {
  3259. textIsOfTypeString = true;
  3260. } else if (Array.isArray(text)) {
  3261. //we don't want to destroy original text array, so cloning it
  3262. var sa = text.concat();
  3263. da = [];
  3264. var len = sa.length;
  3265. var curDa;
  3266. //we do array.join('text that must not be PDFescaped")
  3267. //thus, pdfEscape each component separately
  3268. while (len--) {
  3269. curDa = sa.shift();
  3270. if (
  3271. typeof curDa !== "string" ||
  3272. (Array.isArray(curDa) && typeof curDa[0] !== "string")
  3273. ) {
  3274. tmpTextIsOfTypeString = false;
  3275. }
  3276. }
  3277. textIsOfTypeString = tmpTextIsOfTypeString;
  3278. }
  3279. if (textIsOfTypeString === false) {
  3280. throw new Error(
  3281. 'Type of text must be string or Array. "' +
  3282. text +
  3283. '" is not recognized.'
  3284. );
  3285. }
  3286. //If there are any newlines in text, we assume
  3287. //the user wanted to print multiple lines, so break the
  3288. //text up into an array. If the text is already an array,
  3289. //we assume the user knows what they are doing.
  3290. //Convert text into an array anyway to simplify
  3291. //later code.
  3292. if (typeof text === "string") {
  3293. if (text.match(/[\r?\n]/)) {
  3294. text = text.split(/\r\n|\r|\n/g);
  3295. } else {
  3296. text = [text];
  3297. }
  3298. }
  3299. //baseline
  3300. var height = activeFontSize / scope.internal.scaleFactor;
  3301. var descent = height * (lineHeight - 1);
  3302. switch (options.baseline) {
  3303. case "bottom":
  3304. y -= descent;
  3305. break;
  3306. case "top":
  3307. y += height - descent;
  3308. break;
  3309. case "hanging":
  3310. y += height - 2 * descent;
  3311. break;
  3312. case "middle":
  3313. y += height / 2 - descent;
  3314. break;
  3315. case "ideographic":
  3316. case "alphabetic":
  3317. default:
  3318. // do nothing, everything is fine
  3319. break;
  3320. }
  3321. //multiline
  3322. maxWidth = options.maxWidth || 0;
  3323. if (maxWidth > 0) {
  3324. if (typeof text === "string") {
  3325. text = scope.splitTextToSize(text, maxWidth);
  3326. } else if (Object.prototype.toString.call(text) === "[object Array]") {
  3327. text = text.reduce(function(acc, textLine) {
  3328. return acc.concat(scope.splitTextToSize(textLine, maxWidth));
  3329. }, []);
  3330. }
  3331. }
  3332. //creating Payload-Object to make text byRef
  3333. payload = {
  3334. text: text,
  3335. x: x,
  3336. y: y,
  3337. options: options,
  3338. mutex: {
  3339. pdfEscape: pdfEscape,
  3340. activeFontKey: activeFontKey,
  3341. fonts: fonts,
  3342. activeFontSize: activeFontSize
  3343. }
  3344. };
  3345. events.publish("preProcessText", payload);
  3346. text = payload.text;
  3347. options = payload.options;
  3348. //angle
  3349. angle = options.angle;
  3350. if (
  3351. transformationMatrix instanceof Matrix === false &&
  3352. angle &&
  3353. typeof angle === "number"
  3354. ) {
  3355. angle *= Math.PI / 180;
  3356. if (options.rotationDirection === 0) {
  3357. angle = -angle;
  3358. }
  3359. if (apiMode === ApiMode.ADVANCED) {
  3360. angle = -angle;
  3361. }
  3362. var c = Math.cos(angle);
  3363. var s = Math.sin(angle);
  3364. transformationMatrix = new Matrix(c, s, -s, c, 0, 0);
  3365. } else if (angle && angle instanceof Matrix) {
  3366. transformationMatrix = angle;
  3367. }
  3368. if (apiMode === ApiMode.ADVANCED && !transformationMatrix) {
  3369. transformationMatrix = identityMatrix;
  3370. }
  3371. //charSpace
  3372. charSpace = options.charSpace || activeCharSpace;
  3373. if (typeof charSpace !== "undefined") {
  3374. xtra += hpf(scale(charSpace)) + " Tc\n";
  3375. this.setCharSpace(this.getCharSpace() || 0);
  3376. }
  3377. horizontalScale = options.horizontalScale;
  3378. if (typeof horizontalScale !== "undefined") {
  3379. xtra += hpf(horizontalScale * 100) + " Tz\n";
  3380. }
  3381. //lang
  3382. var lang = options.lang;
  3383. if (lang) {
  3384. // xtra += "/Lang (" + lang +")\n";
  3385. }
  3386. //renderingMode
  3387. var renderingMode = -1;
  3388. var parmRenderingMode =
  3389. typeof options.renderingMode !== "undefined"
  3390. ? options.renderingMode
  3391. : options.stroke;
  3392. var pageContext = scope.internal.getCurrentPageInfo().pageContext;
  3393. switch (parmRenderingMode) {
  3394. case 0:
  3395. case false:
  3396. case "fill":
  3397. renderingMode = 0;
  3398. break;
  3399. case 1:
  3400. case true:
  3401. case "stroke":
  3402. renderingMode = 1;
  3403. break;
  3404. case 2:
  3405. case "fillThenStroke":
  3406. renderingMode = 2;
  3407. break;
  3408. case 3:
  3409. case "invisible":
  3410. renderingMode = 3;
  3411. break;
  3412. case 4:
  3413. case "fillAndAddForClipping":
  3414. renderingMode = 4;
  3415. break;
  3416. case 5:
  3417. case "strokeAndAddPathForClipping":
  3418. renderingMode = 5;
  3419. break;
  3420. case 6:
  3421. case "fillThenStrokeAndAddToPathForClipping":
  3422. renderingMode = 6;
  3423. break;
  3424. case 7:
  3425. case "addToPathForClipping":
  3426. renderingMode = 7;
  3427. break;
  3428. }
  3429. var usedRenderingMode =
  3430. typeof pageContext.usedRenderingMode !== "undefined"
  3431. ? pageContext.usedRenderingMode
  3432. : -1;
  3433. //if the coder wrote it explicitly to use a specific
  3434. //renderingMode, then use it
  3435. if (renderingMode !== -1) {
  3436. xtra += renderingMode + " Tr\n";
  3437. //otherwise check if we used the rendering Mode already
  3438. //if so then set the rendering Mode...
  3439. } else if (usedRenderingMode !== -1) {
  3440. xtra += "0 Tr\n";
  3441. }
  3442. if (renderingMode !== -1) {
  3443. pageContext.usedRenderingMode = renderingMode;
  3444. }
  3445. //align
  3446. align = options.align || "left";
  3447. var leading = activeFontSize * lineHeight;
  3448. var pageWidth = scope.internal.pageSize.getWidth();
  3449. var activeFont = fonts[activeFontKey];
  3450. charSpace = options.charSpace || activeCharSpace;
  3451. maxWidth = options.maxWidth || 0;
  3452. var lineWidths;
  3453. flags = Object.assign({ autoencode: true, noBOM: true }, options.flags);
  3454. var wordSpacingPerLine = [];
  3455. var findWidth = function(v) {
  3456. return (
  3457. (scope.getStringUnitWidth(v, {
  3458. font: activeFont,
  3459. charSpace: charSpace,
  3460. fontSize: activeFontSize,
  3461. doKerning: false
  3462. }) *
  3463. activeFontSize) /
  3464. scaleFactor
  3465. );
  3466. };
  3467. if (Object.prototype.toString.call(text) === "[object Array]") {
  3468. da = transformTextToSpecialArray(text);
  3469. var newY;
  3470. if (align !== "left") {
  3471. lineWidths = da.map(findWidth);
  3472. }
  3473. //The first line uses the "main" Td setting,
  3474. //and the subsequent lines are offset by the
  3475. //previous line's x coordinate.
  3476. var prevWidth = 0;
  3477. var newX;
  3478. if (align === "right") {
  3479. //The passed in x coordinate defines the
  3480. //rightmost point of the text.
  3481. x -= lineWidths[0];
  3482. text = [];
  3483. len = da.length;
  3484. for (var i = 0; i < len; i++) {
  3485. if (i === 0) {
  3486. newX = getHorizontalCoordinate(x);
  3487. newY = getVerticalCoordinate(y);
  3488. } else {
  3489. newX = scale(prevWidth - lineWidths[i]);
  3490. newY = -leading;
  3491. }
  3492. text.push([da[i], newX, newY]);
  3493. prevWidth = lineWidths[i];
  3494. }
  3495. } else if (align === "center") {
  3496. //The passed in x coordinate defines
  3497. //the center point.
  3498. x -= lineWidths[0] / 2;
  3499. text = [];
  3500. len = da.length;
  3501. for (var j = 0; j < len; j++) {
  3502. if (j === 0) {
  3503. newX = getHorizontalCoordinate(x);
  3504. newY = getVerticalCoordinate(y);
  3505. } else {
  3506. newX = scale((prevWidth - lineWidths[j]) / 2);
  3507. newY = -leading;
  3508. }
  3509. text.push([da[j], newX, newY]);
  3510. prevWidth = lineWidths[j];
  3511. }
  3512. } else if (align === "left") {
  3513. text = [];
  3514. len = da.length;
  3515. for (var h = 0; h < len; h++) {
  3516. text.push(da[h]);
  3517. }
  3518. } else if (align === "justify" && activeFont.encoding === "Identity-H") {
  3519. // when using unicode fonts, wordSpacePerLine does not apply
  3520. text = [];
  3521. len = da.length;
  3522. maxWidth = maxWidth !== 0 ? maxWidth : pageWidth;
  3523. let backToStartX = 0;
  3524. for (var l = 0; l < len; l++) {
  3525. newY = l === 0 ? getVerticalCoordinate(y) : -leading;
  3526. newX = l === 0 ? getHorizontalCoordinate(x) : backToStartX;
  3527. if (l < len - 1) {
  3528. let spacing = scale(
  3529. (maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1)
  3530. );
  3531. let words = da[l].split(" ");
  3532. text.push([words[0] + " ", newX, newY]);
  3533. backToStartX = 0; // distance to reset back to the left
  3534. for (let i = 1; i < words.length; i++) {
  3535. let shiftAmount =
  3536. (findWidth(words[i - 1] + " " + words[i]) -
  3537. findWidth(words[i])) *
  3538. scaleFactor +
  3539. spacing;
  3540. if (i == words.length - 1) text.push([words[i], shiftAmount, 0]);
  3541. else text.push([words[i] + " ", shiftAmount, 0]);
  3542. backToStartX -= shiftAmount;
  3543. }
  3544. } else {
  3545. text.push([da[l], newX, newY]);
  3546. }
  3547. }
  3548. text.push(["", backToStartX, 0]);
  3549. } else if (align === "justify") {
  3550. text = [];
  3551. len = da.length;
  3552. maxWidth = maxWidth !== 0 ? maxWidth : pageWidth;
  3553. for (var l = 0; l < len; l++) {
  3554. newY = l === 0 ? getVerticalCoordinate(y) : -leading;
  3555. newX = l === 0 ? getHorizontalCoordinate(x) : 0;
  3556. if (l < len - 1) {
  3557. wordSpacingPerLine.push(
  3558. hpf(
  3559. scale(
  3560. (maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1)
  3561. )
  3562. )
  3563. );
  3564. } else {
  3565. wordSpacingPerLine.push(0);
  3566. }
  3567. text.push([da[l], newX, newY]);
  3568. }
  3569. } else {
  3570. throw new Error(
  3571. 'Unrecognized alignment option, use "left", "center", "right" or "justify".'
  3572. );
  3573. }
  3574. }
  3575. //R2L
  3576. var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L;
  3577. if (doReversing === true) {
  3578. text = processTextByFunction(text, function(text, posX, posY) {
  3579. return [
  3580. text
  3581. .split("")
  3582. .reverse()
  3583. .join(""),
  3584. posX,
  3585. posY
  3586. ];
  3587. });
  3588. }
  3589. //creating Payload-Object to make text byRef
  3590. payload = {
  3591. text: text,
  3592. x: x,
  3593. y: y,
  3594. options: options,
  3595. mutex: {
  3596. pdfEscape: pdfEscape,
  3597. activeFontKey: activeFontKey,
  3598. fonts: fonts,
  3599. activeFontSize: activeFontSize
  3600. }
  3601. };
  3602. events.publish("postProcessText", payload);
  3603. text = payload.text;
  3604. isHex = payload.mutex.isHex || false;
  3605. //Escaping
  3606. var activeFontEncoding = fonts[activeFontKey].encoding;
  3607. if (
  3608. activeFontEncoding === "WinAnsiEncoding" ||
  3609. activeFontEncoding === "StandardEncoding"
  3610. ) {
  3611. text = processTextByFunction(text, function(text, posX, posY) {
  3612. return [ESC(text), posX, posY];
  3613. });
  3614. }
  3615. da = transformTextToSpecialArray(text);
  3616. text = [];
  3617. var STRING = 0;
  3618. var ARRAY = 1;
  3619. var variant = Array.isArray(da[0]) ? ARRAY : STRING;
  3620. var posX;
  3621. var posY;
  3622. var content;
  3623. var wordSpacing = "";
  3624. var generatePosition = function(
  3625. parmPosX,
  3626. parmPosY,
  3627. parmTransformationMatrix
  3628. ) {
  3629. var position = "";
  3630. if (parmTransformationMatrix instanceof Matrix) {
  3631. // It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y
  3632. // but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied
  3633. // in the coordinate system established by this matrix
  3634. if (typeof options.angle === "number") {
  3635. parmTransformationMatrix = matrixMult(
  3636. parmTransformationMatrix,
  3637. new Matrix(1, 0, 0, 1, parmPosX, parmPosY)
  3638. );
  3639. } else {
  3640. parmTransformationMatrix = matrixMult(
  3641. new Matrix(1, 0, 0, 1, parmPosX, parmPosY),
  3642. parmTransformationMatrix
  3643. );
  3644. }
  3645. if (apiMode === ApiMode.ADVANCED) {
  3646. parmTransformationMatrix = matrixMult(
  3647. new Matrix(1, 0, 0, -1, 0, 0),
  3648. parmTransformationMatrix
  3649. );
  3650. }
  3651. position = parmTransformationMatrix.join(" ") + " Tm\n";
  3652. } else {
  3653. position = hpf(parmPosX) + " " + hpf(parmPosY) + " Td\n";
  3654. }
  3655. return position;
  3656. };
  3657. for (var lineIndex = 0; lineIndex < da.length; lineIndex++) {
  3658. wordSpacing = "";
  3659. switch (variant) {
  3660. case ARRAY:
  3661. content =
  3662. (isHex ? "<" : "(") + da[lineIndex][0] + (isHex ? ">" : ")");
  3663. posX = parseFloat(da[lineIndex][1]);
  3664. posY = parseFloat(da[lineIndex][2]);
  3665. break;
  3666. case STRING:
  3667. content = (isHex ? "<" : "(") + da[lineIndex] + (isHex ? ">" : ")");
  3668. posX = getHorizontalCoordinate(x);
  3669. posY = getVerticalCoordinate(y);
  3670. break;
  3671. }
  3672. if (
  3673. typeof wordSpacingPerLine !== "undefined" &&
  3674. typeof wordSpacingPerLine[lineIndex] !== "undefined"
  3675. ) {
  3676. wordSpacing = wordSpacingPerLine[lineIndex] + " Tw\n";
  3677. }
  3678. if (lineIndex === 0) {
  3679. text.push(
  3680. wordSpacing +
  3681. generatePosition(posX, posY, transformationMatrix) +
  3682. content
  3683. );
  3684. } else if (variant === STRING) {
  3685. text.push(wordSpacing + content);
  3686. } else if (variant === ARRAY) {
  3687. text.push(
  3688. wordSpacing +
  3689. generatePosition(posX, posY, transformationMatrix) +
  3690. content
  3691. );
  3692. }
  3693. }
  3694. text = variant === STRING ? text.join(" Tj\nT* ") : text.join(" Tj\n");
  3695. text += " Tj\n";
  3696. var result = "BT\n/";
  3697. result += activeFontKey + " " + activeFontSize + " Tf\n"; // font face, style, size
  3698. result += hpf(activeFontSize * lineHeight) + " TL\n"; // line spacing
  3699. result += textColor + "\n";
  3700. result += xtra;
  3701. result += text;
  3702. result += "ET";
  3703. out(result);
  3704. usedFonts[activeFontKey] = true;
  3705. return scope;
  3706. };
  3707. // PDF supports these path painting and clip path operators:
  3708. //
  3709. // S - stroke
  3710. // s - close/stroke
  3711. // f (F) - fill non-zero
  3712. // f* - fill evenodd
  3713. // B - fill stroke nonzero
  3714. // B* - fill stroke evenodd
  3715. // b - close fill stroke nonzero
  3716. // b* - close fill stroke evenodd
  3717. // n - nothing (consume path)
  3718. // W - clip nonzero
  3719. // W* - clip evenodd
  3720. //
  3721. // In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close()
  3722. // method.
  3723. /**
  3724. *
  3725. * @name clip
  3726. * @function
  3727. * @instance
  3728. * @param {string} rule Only possible value is 'evenodd'
  3729. * @returns {jsPDF}
  3730. * @memberof jsPDF#
  3731. * @description All .clip() after calling drawing ops with a style argument of null.
  3732. */
  3733. var clip = (API.__private__.clip = API.clip = function(rule) {
  3734. // Call .clip() after calling drawing ops with a style argument of null
  3735. // W is the PDF clipping op
  3736. if ("evenodd" === rule) {
  3737. out("W*");
  3738. } else {
  3739. out("W");
  3740. }
  3741. return this;
  3742. });
  3743. /**
  3744. * @name clipEvenOdd
  3745. * @function
  3746. * @instance
  3747. * @returns {jsPDF}
  3748. * @memberof jsPDF#
  3749. * @description Modify the current clip path by intersecting it with the current path using the even-odd rule. Note
  3750. * that this will NOT consume the current path. In order to only use this path for clipping call
  3751. * {@link API.discardPath} afterwards.
  3752. */
  3753. API.clipEvenOdd = function() {
  3754. return clip("evenodd");
  3755. };
  3756. /**
  3757. * Consumes the current path without any effect. Mainly used in combination with {@link clip} or
  3758. * {@link clipEvenOdd}. The PDF "n" operator.
  3759. * @name discardPath
  3760. * @function
  3761. * @instance
  3762. * @returns {jsPDF}
  3763. * @memberof jsPDF#
  3764. */
  3765. API.__private__.discardPath = API.discardPath = function() {
  3766. out("n");
  3767. return this;
  3768. };
  3769. var isValidStyle = (API.__private__.isValidStyle = function(style) {
  3770. var validStyleVariants = [
  3771. undefined,
  3772. null,
  3773. "S",
  3774. "D",
  3775. "F",
  3776. "DF",
  3777. "FD",
  3778. "f",
  3779. "f*",
  3780. "B",
  3781. "B*",
  3782. "n"
  3783. ];
  3784. var result = false;
  3785. if (validStyleVariants.indexOf(style) !== -1) {
  3786. result = true;
  3787. }
  3788. return result;
  3789. });
  3790. API.__private__.setDefaultPathOperation = API.setDefaultPathOperation = function(
  3791. operator
  3792. ) {
  3793. if (isValidStyle(operator)) {
  3794. defaultPathOperation = operator;
  3795. }
  3796. return this;
  3797. };
  3798. var getStyle = (API.__private__.getStyle = API.getStyle = function(style) {
  3799. // see path-painting operators in PDF spec
  3800. var op = defaultPathOperation; // stroke
  3801. switch (style) {
  3802. case "D":
  3803. case "S":
  3804. op = "S"; // stroke
  3805. break;
  3806. case "F":
  3807. op = "f"; // fill
  3808. break;
  3809. case "FD":
  3810. case "DF":
  3811. op = "B";
  3812. break;
  3813. case "f":
  3814. case "f*":
  3815. case "B":
  3816. case "B*":
  3817. /*
  3818. Allow direct use of these PDF path-painting operators:
  3819. - f fill using nonzero winding number rule
  3820. - f* fill using even-odd rule
  3821. - B fill then stroke with fill using non-zero winding number rule
  3822. - B* fill then stroke with fill using even-odd rule
  3823. */
  3824. op = style;
  3825. break;
  3826. }
  3827. return op;
  3828. });
  3829. /**
  3830. * Close the current path. The PDF "h" operator.
  3831. * @name close
  3832. * @function
  3833. * @instance
  3834. * @returns {jsPDF}
  3835. * @memberof jsPDF#
  3836. */
  3837. var close = (API.close = function() {
  3838. out("h");
  3839. return this;
  3840. });
  3841. /**
  3842. * Stroke the path. The PDF "S" operator.
  3843. * @name stroke
  3844. * @function
  3845. * @instance
  3846. * @returns {jsPDF}
  3847. * @memberof jsPDF#
  3848. */
  3849. API.stroke = function() {
  3850. out("S");
  3851. return this;
  3852. };
  3853. /**
  3854. * Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled
  3855. * with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator.
  3856. * @name fill
  3857. * @function
  3858. * @instance
  3859. * @param {PatternData=} pattern If provided the path will be filled with this pattern
  3860. * @returns {jsPDF}
  3861. * @memberof jsPDF#
  3862. */
  3863. API.fill = function(pattern) {
  3864. fillWithOptionalPattern("f", pattern);
  3865. return this;
  3866. };
  3867. /**
  3868. * Fill the current path using the even-odd rule. The PDF f* operator.
  3869. * @see API.fill
  3870. * @name fillEvenOdd
  3871. * @function
  3872. * @instance
  3873. * @param {PatternData=} pattern If provided the path will be filled with this pattern
  3874. * @returns {jsPDF}
  3875. * @memberof jsPDF#
  3876. */
  3877. API.fillEvenOdd = function(pattern) {
  3878. fillWithOptionalPattern("f*", pattern);
  3879. return this;
  3880. };
  3881. /**
  3882. * Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator.
  3883. * @see API.fill
  3884. * @name fillStroke
  3885. * @function
  3886. * @instance
  3887. * @param {PatternData=} pattern If provided the path will be stroked with this pattern
  3888. * @returns {jsPDF}
  3889. * @memberof jsPDF#
  3890. */
  3891. API.fillStroke = function(pattern) {
  3892. fillWithOptionalPattern("B", pattern);
  3893. return this;
  3894. };
  3895. /**
  3896. * Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator.
  3897. * @see API.fill
  3898. * @name fillStrokeEvenOdd
  3899. * @function
  3900. * @instance
  3901. * @param {PatternData=} pattern If provided the path will be fill-stroked with this pattern
  3902. * @returns {jsPDF}
  3903. * @memberof jsPDF#
  3904. */
  3905. API.fillStrokeEvenOdd = function(pattern) {
  3906. fillWithOptionalPattern("B*", pattern);
  3907. return this;
  3908. };
  3909. var fillWithOptionalPattern = function(style, pattern) {
  3910. if (typeof pattern === "object") {
  3911. fillWithPattern(pattern, style);
  3912. } else {
  3913. out(style);
  3914. }
  3915. };
  3916. var putStyle = function(style) {
  3917. if (
  3918. style === null ||
  3919. (apiMode === ApiMode.ADVANCED && style === undefined)
  3920. ) {
  3921. return;
  3922. }
  3923. style = getStyle(style);
  3924. // stroking / filling / both the path
  3925. out(style);
  3926. };
  3927. function cloneTilingPattern(patternKey, boundingBox, xStep, yStep, matrix) {
  3928. var clone = new TilingPattern(
  3929. boundingBox || this.boundingBox,
  3930. xStep || this.xStep,
  3931. yStep || this.yStep,
  3932. this.gState,
  3933. matrix || this.matrix
  3934. );
  3935. clone.stream = this.stream;
  3936. var key = patternKey + "$$" + this.cloneIndex++ + "$$";
  3937. addPattern(key, clone);
  3938. return clone;
  3939. }
  3940. var fillWithPattern = function(patternData, style) {
  3941. var patternId = patternMap[patternData.key];
  3942. var pattern = patterns[patternId];
  3943. if (pattern instanceof ShadingPattern) {
  3944. out("q");
  3945. out(clipRuleFromStyle(style));
  3946. if (pattern.gState) {
  3947. API.setGState(pattern.gState);
  3948. }
  3949. out(patternData.matrix.toString() + " cm");
  3950. out("/" + patternId + " sh");
  3951. out("Q");
  3952. } else if (pattern instanceof TilingPattern) {
  3953. // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation,
  3954. // so we must flip them
  3955. var matrix = new Matrix(1, 0, 0, -1, 0, getPageHeight());
  3956. if (patternData.matrix) {
  3957. matrix = matrix.multiply(patternData.matrix || identityMatrix);
  3958. // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances
  3959. // for each use
  3960. patternId = cloneTilingPattern.call(
  3961. pattern,
  3962. patternData.key,
  3963. patternData.boundingBox,
  3964. patternData.xStep,
  3965. patternData.yStep,
  3966. matrix
  3967. ).id;
  3968. }
  3969. out("q");
  3970. out("/Pattern cs");
  3971. out("/" + patternId + " scn");
  3972. if (pattern.gState) {
  3973. API.setGState(pattern.gState);
  3974. }
  3975. out(style);
  3976. out("Q");
  3977. }
  3978. };
  3979. var clipRuleFromStyle = function(style) {
  3980. switch (style) {
  3981. case "f":
  3982. case "F":
  3983. return "W n";
  3984. case "f*":
  3985. return "W* n";
  3986. case "B":
  3987. return "W S";
  3988. case "B*":
  3989. return "W* S";
  3990. // these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern
  3991. // and "n"/"S" as style would still fill/fill and stroke the path)
  3992. case "S":
  3993. return "W S";
  3994. case "n":
  3995. return "W n";
  3996. }
  3997. };
  3998. /**
  3999. * Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator.
  4000. * @param {number} x
  4001. * @param {number} y
  4002. * @name moveTo
  4003. * @function
  4004. * @instance
  4005. * @memberof jsPDF#
  4006. * @returns {jsPDF}
  4007. */
  4008. var moveTo = (API.moveTo = function(x, y) {
  4009. out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " m");
  4010. return this;
  4011. });
  4012. /**
  4013. * Append a straight line segment from the current point to the point (x, y). The PDF "l" operator.
  4014. * @param {number} x
  4015. * @param {number} y
  4016. * @memberof jsPDF#
  4017. * @name lineTo
  4018. * @function
  4019. * @instance
  4020. * @memberof jsPDF#
  4021. * @returns {jsPDF}
  4022. */
  4023. var lineTo = (API.lineTo = function(x, y) {
  4024. out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " l");
  4025. return this;
  4026. });
  4027. /**
  4028. * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point
  4029. * (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3).
  4030. * @param {number} x1
  4031. * @param {number} y1
  4032. * @param {number} x2
  4033. * @param {number} y2
  4034. * @param {number} x3
  4035. * @param {number} y3
  4036. * @memberof jsPDF#
  4037. * @name curveTo
  4038. * @function
  4039. * @instance
  4040. * @memberof jsPDF#
  4041. * @returns {jsPDF}
  4042. */
  4043. var curveTo = (API.curveTo = function(x1, y1, x2, y2, x3, y3) {
  4044. out(
  4045. [
  4046. hpf(scale(x1)),
  4047. hpf(transformScaleY(y1)),
  4048. hpf(scale(x2)),
  4049. hpf(transformScaleY(y2)),
  4050. hpf(scale(x3)),
  4051. hpf(transformScaleY(y3)),
  4052. "c"
  4053. ].join(" ")
  4054. );
  4055. return this;
  4056. });
  4057. /**
  4058. * Draw a line on the current page.
  4059. *
  4060. * @name line
  4061. * @function
  4062. * @instance
  4063. * @param {number} x1
  4064. * @param {number} y1
  4065. * @param {number} x2
  4066. * @param {number} y2
  4067. * @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. default: 'S'
  4068. * @returns {jsPDF}
  4069. * @memberof jsPDF#
  4070. */
  4071. API.__private__.line = API.line = function(x1, y1, x2, y2, style) {
  4072. if (
  4073. isNaN(x1) ||
  4074. isNaN(y1) ||
  4075. isNaN(x2) ||
  4076. isNaN(y2) ||
  4077. !isValidStyle(style)
  4078. ) {
  4079. throw new Error("Invalid arguments passed to jsPDF.line");
  4080. }
  4081. if (apiMode === ApiMode.COMPAT) {
  4082. return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], style || "S");
  4083. } else {
  4084. return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke();
  4085. }
  4086. };
  4087. /**
  4088. * @typedef {Object} PatternData
  4089. * {Matrix|undefined} matrix
  4090. * {Number|undefined} xStep
  4091. * {Number|undefined} yStep
  4092. * {Array.<Number>|undefined} boundingBox
  4093. */
  4094. /**
  4095. * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates.
  4096. * All data points in `lines` are relative to last line origin.
  4097. * `x`, `y` become x1,y1 for first line / curve in the set.
  4098. * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point.
  4099. * 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.
  4100. *
  4101. * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, [1,1], 'F', false) // line, line, bezier curve, line
  4102. * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves).
  4103. * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
  4104. * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4105. * @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.
  4106. * @param {string=} style A string specifying the painting style or null. Valid styles include:
  4107. * 'S' [default] - stroke,
  4108. * 'F' - fill,
  4109. * and 'DF' (or 'FD') - fill then stroke.
  4110. * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
  4111. * method calls. The last drawing method call used to define the shape should not have a null style argument.
  4112. *
  4113. * In "advanced" API mode this parameter is deprecated.
  4114. * @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
  4115. * @function
  4116. * @instance
  4117. * @returns {jsPDF}
  4118. * @memberof jsPDF#
  4119. * @name lines
  4120. */
  4121. API.__private__.lines = API.lines = function(
  4122. lines,
  4123. x,
  4124. y,
  4125. scale,
  4126. style,
  4127. closed
  4128. ) {
  4129. var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4, tmp;
  4130. // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style)
  4131. // in effort to make all calls have similar signature like
  4132. // function(content, coordinateX, coordinateY , miscellaneous)
  4133. // this method had its args flipped.
  4134. // code below allows backward compatibility with old arg order.
  4135. if (typeof lines === "number") {
  4136. tmp = y;
  4137. y = x;
  4138. x = lines;
  4139. lines = tmp;
  4140. }
  4141. scale = scale || [1, 1];
  4142. closed = closed || false;
  4143. if (
  4144. isNaN(x) ||
  4145. isNaN(y) ||
  4146. !Array.isArray(lines) ||
  4147. !Array.isArray(scale) ||
  4148. !isValidStyle(style) ||
  4149. typeof closed !== "boolean"
  4150. ) {
  4151. throw new Error("Invalid arguments passed to jsPDF.lines");
  4152. }
  4153. // starting point
  4154. moveTo(x, y);
  4155. scalex = scale[0];
  4156. scaley = scale[1];
  4157. l = lines.length;
  4158. //, x2, y2 // bezier only. In page default measurement "units", *after* scaling
  4159. //, x3, y3 // bezier only. In page default measurement "units", *after* scaling
  4160. // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling
  4161. x4 = x; // last / ending point = starting point for first item.
  4162. y4 = y; // last / ending point = starting point for first item.
  4163. for (i = 0; i < l; i++) {
  4164. leg = lines[i];
  4165. if (leg.length === 2) {
  4166. // simple line
  4167. x4 = leg[0] * scalex + x4; // here last x4 was prior ending point
  4168. y4 = leg[1] * scaley + y4; // here last y4 was prior ending point
  4169. lineTo(x4, y4);
  4170. } else {
  4171. // bezier curve
  4172. x2 = leg[0] * scalex + x4; // here last x4 is prior ending point
  4173. y2 = leg[1] * scaley + y4; // here last y4 is prior ending point
  4174. x3 = leg[2] * scalex + x4; // here last x4 is prior ending point
  4175. y3 = leg[3] * scaley + y4; // here last y4 is prior ending point
  4176. x4 = leg[4] * scalex + x4; // here last x4 was prior ending point
  4177. y4 = leg[5] * scaley + y4; // here last y4 was prior ending point
  4178. curveTo(x2, y2, x3, y3, x4, y4);
  4179. }
  4180. }
  4181. if (closed) {
  4182. close();
  4183. }
  4184. putStyle(style);
  4185. return this;
  4186. };
  4187. /**
  4188. * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative.
  4189. * @param {Array<Object>} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to)
  4190. * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c"
  4191. * six and "h" an empty array (or undefined).
  4192. * @function
  4193. * @returns {jsPDF}
  4194. * @memberof jsPDF#
  4195. * @name path
  4196. */
  4197. API.path = function(lines) {
  4198. for (var i = 0; i < lines.length; i++) {
  4199. var leg = lines[i];
  4200. var coords = leg.c;
  4201. switch (leg.op) {
  4202. case "m":
  4203. moveTo(coords[0], coords[1]);
  4204. break;
  4205. case "l":
  4206. lineTo(coords[0], coords[1]);
  4207. break;
  4208. case "c":
  4209. curveTo.apply(this, coords);
  4210. break;
  4211. case "h":
  4212. close();
  4213. break;
  4214. }
  4215. }
  4216. return this;
  4217. };
  4218. /**
  4219. * Adds a rectangle to PDF.
  4220. *
  4221. * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
  4222. * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4223. * @param {number} w Width (in units declared at inception of PDF document)
  4224. * @param {number} h Height (in units declared at inception of PDF document)
  4225. * @param {string=} style A string specifying the painting style or null. Valid styles include:
  4226. * 'S' [default] - stroke,
  4227. * 'F' - fill,
  4228. * and 'DF' (or 'FD') - fill then stroke.
  4229. * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
  4230. * method calls. The last drawing method call used to define the shape should not have a null style argument.
  4231. *
  4232. * In "advanced" API mode this parameter is deprecated.
  4233. * @function
  4234. * @instance
  4235. * @returns {jsPDF}
  4236. * @memberof jsPDF#
  4237. * @name rect
  4238. */
  4239. API.__private__.rect = API.rect = function(x, y, w, h, style) {
  4240. if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h) || !isValidStyle(style)) {
  4241. throw new Error("Invalid arguments passed to jsPDF.rect");
  4242. }
  4243. if (apiMode === ApiMode.COMPAT) {
  4244. h = -h;
  4245. }
  4246. out(
  4247. [
  4248. hpf(scale(x)),
  4249. hpf(transformScaleY(y)),
  4250. hpf(scale(w)),
  4251. hpf(scale(h)),
  4252. "re"
  4253. ].join(" ")
  4254. );
  4255. putStyle(style);
  4256. return this;
  4257. };
  4258. /**
  4259. * Adds a triangle to PDF.
  4260. *
  4261. * @param {number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page
  4262. * @param {number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4263. * @param {number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page
  4264. * @param {number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4265. * @param {number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page
  4266. * @param {number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4267. * @param {string=} style A string specifying the painting style or null. Valid styles include:
  4268. * 'S' [default] - stroke,
  4269. * 'F' - fill,
  4270. * and 'DF' (or 'FD') - fill then stroke.
  4271. * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
  4272. * method calls. The last drawing method call used to define the shape should not have a null style argument.
  4273. *
  4274. * In "advanced" API mode this parameter is deprecated.
  4275. * @function
  4276. * @instance
  4277. * @returns {jsPDF}
  4278. * @memberof jsPDF#
  4279. * @name triangle
  4280. */
  4281. API.__private__.triangle = API.triangle = function(
  4282. x1,
  4283. y1,
  4284. x2,
  4285. y2,
  4286. x3,
  4287. y3,
  4288. style
  4289. ) {
  4290. if (
  4291. isNaN(x1) ||
  4292. isNaN(y1) ||
  4293. isNaN(x2) ||
  4294. isNaN(y2) ||
  4295. isNaN(x3) ||
  4296. isNaN(y3) ||
  4297. !isValidStyle(style)
  4298. ) {
  4299. throw new Error("Invalid arguments passed to jsPDF.triangle");
  4300. }
  4301. this.lines(
  4302. [
  4303. [x2 - x1, y2 - y1], // vector to point 2
  4304. [x3 - x2, y3 - y2], // vector to point 3
  4305. [x1 - x3, y1 - y3] // closing vector back to point 1
  4306. ],
  4307. x1,
  4308. y1, // start of path
  4309. [1, 1],
  4310. style,
  4311. true
  4312. );
  4313. return this;
  4314. };
  4315. /**
  4316. * Adds a rectangle with rounded corners to PDF.
  4317. *
  4318. * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
  4319. * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4320. * @param {number} w Width (in units declared at inception of PDF document)
  4321. * @param {number} h Height (in units declared at inception of PDF document)
  4322. * @param {number} rx Radius along x axis (in units declared at inception of PDF document)
  4323. * @param {number} ry Radius along y axis (in units declared at inception of PDF document)
  4324. * @param {string=} style A string specifying the painting style or null. Valid styles include:
  4325. * 'S' [default] - stroke,
  4326. * 'F' - fill,
  4327. * and 'DF' (or 'FD') - fill then stroke.
  4328. * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
  4329. * method calls. The last drawing method call used to define the shape should not have a null style argument.
  4330. *
  4331. * In "advanced" API mode this parameter is deprecated.
  4332. * @function
  4333. * @instance
  4334. * @returns {jsPDF}
  4335. * @memberof jsPDF#
  4336. * @name roundedRect
  4337. */
  4338. API.__private__.roundedRect = API.roundedRect = function(
  4339. x,
  4340. y,
  4341. w,
  4342. h,
  4343. rx,
  4344. ry,
  4345. style
  4346. ) {
  4347. if (
  4348. isNaN(x) ||
  4349. isNaN(y) ||
  4350. isNaN(w) ||
  4351. isNaN(h) ||
  4352. isNaN(rx) ||
  4353. isNaN(ry) ||
  4354. !isValidStyle(style)
  4355. ) {
  4356. throw new Error("Invalid arguments passed to jsPDF.roundedRect");
  4357. }
  4358. var MyArc = (4 / 3) * (Math.SQRT2 - 1);
  4359. rx = Math.min(rx, w * 0.5);
  4360. ry = Math.min(ry, h * 0.5);
  4361. this.lines(
  4362. [
  4363. [w - 2 * rx, 0],
  4364. [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry],
  4365. [0, h - 2 * ry],
  4366. [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry],
  4367. [-w + 2 * rx, 0],
  4368. [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry],
  4369. [0, -h + 2 * ry],
  4370. [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]
  4371. ],
  4372. x + rx,
  4373. y, // start of path
  4374. [1, 1],
  4375. style,
  4376. true
  4377. );
  4378. return this;
  4379. };
  4380. /**
  4381. * Adds an ellipse to PDF.
  4382. *
  4383. * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
  4384. * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4385. * @param {number} rx Radius along x axis (in units declared at inception of PDF document)
  4386. * @param {number} ry Radius along y axis (in units declared at inception of PDF document)
  4387. * @param {string=} style A string specifying the painting style or null. Valid styles include:
  4388. * 'S' [default] - stroke,
  4389. * 'F' - fill,
  4390. * and 'DF' (or 'FD') - fill then stroke.
  4391. * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
  4392. * method calls. The last drawing method call used to define the shape should not have a null style argument.
  4393. *
  4394. * In "advanced" API mode this parameter is deprecated.
  4395. * @function
  4396. * @instance
  4397. * @returns {jsPDF}
  4398. * @memberof jsPDF#
  4399. * @name ellipse
  4400. */
  4401. API.__private__.ellipse = API.ellipse = function(x, y, rx, ry, style) {
  4402. if (
  4403. isNaN(x) ||
  4404. isNaN(y) ||
  4405. isNaN(rx) ||
  4406. isNaN(ry) ||
  4407. !isValidStyle(style)
  4408. ) {
  4409. throw new Error("Invalid arguments passed to jsPDF.ellipse");
  4410. }
  4411. var lx = (4 / 3) * (Math.SQRT2 - 1) * rx,
  4412. ly = (4 / 3) * (Math.SQRT2 - 1) * ry;
  4413. moveTo(x + rx, y);
  4414. curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry);
  4415. curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y);
  4416. curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry);
  4417. curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y);
  4418. putStyle(style);
  4419. return this;
  4420. };
  4421. /**
  4422. * Adds an circle to PDF.
  4423. *
  4424. * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
  4425. * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
  4426. * @param {number} r Radius (in units declared at inception of PDF document)
  4427. * @param {string=} style A string specifying the painting style or null. Valid styles include:
  4428. * 'S' [default] - stroke,
  4429. * 'F' - fill,
  4430. * and 'DF' (or 'FD') - fill then stroke.
  4431. * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
  4432. * method calls. The last drawing method call used to define the shape should not have a null style argument.
  4433. *
  4434. * In "advanced" API mode this parameter is deprecated.
  4435. * @function
  4436. * @instance
  4437. * @returns {jsPDF}
  4438. * @memberof jsPDF#
  4439. * @name circle
  4440. */
  4441. API.__private__.circle = API.circle = function(x, y, r, style) {
  4442. if (isNaN(x) || isNaN(y) || isNaN(r) || !isValidStyle(style)) {
  4443. throw new Error("Invalid arguments passed to jsPDF.circle");
  4444. }
  4445. return this.ellipse(x, y, r, r, style);
  4446. };
  4447. /**
  4448. * Sets text font face, variant for upcoming text elements.
  4449. * See output of jsPDF.getFontList() for possible font names, styles.
  4450. *
  4451. * @param {string} fontName Font name or family. Example: "times".
  4452. * @param {string} fontStyle Font style or variant. Example: "italic".
  4453. * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400
  4454. * @function
  4455. * @instance
  4456. * @returns {jsPDF}
  4457. * @memberof jsPDF#
  4458. * @name setFont
  4459. */
  4460. API.setFont = function(fontName, fontStyle, fontWeight) {
  4461. if (fontWeight) {
  4462. fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight);
  4463. }
  4464. activeFontKey = getFont(fontName, fontStyle, {
  4465. disableWarning: false
  4466. });
  4467. return this;
  4468. };
  4469. /**
  4470. * Gets text font face, variant for upcoming text elements.
  4471. *
  4472. * @function
  4473. * @instance
  4474. * @returns {Object}
  4475. * @memberof jsPDF#
  4476. * @name getFont
  4477. */
  4478. var getFontEntry = (API.__private__.getFont = API.getFont = function() {
  4479. return fonts[getFont.apply(API, arguments)];
  4480. });
  4481. /**
  4482. * Returns an object - a tree of fontName to fontStyle relationships available to
  4483. * active PDF document.
  4484. *
  4485. * @public
  4486. * @function
  4487. * @instance
  4488. * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... }
  4489. * @memberof jsPDF#
  4490. * @name getFontList
  4491. */
  4492. API.__private__.getFontList = API.getFontList = function() {
  4493. var list = {},
  4494. fontName,
  4495. fontStyle;
  4496. for (fontName in fontmap) {
  4497. if (fontmap.hasOwnProperty(fontName)) {
  4498. list[fontName] = [];
  4499. for (fontStyle in fontmap[fontName]) {
  4500. if (fontmap[fontName].hasOwnProperty(fontStyle)) {
  4501. list[fontName].push(fontStyle);
  4502. }
  4503. }
  4504. }
  4505. }
  4506. return list;
  4507. };
  4508. /**
  4509. * Add a custom font to the current instance.
  4510. *
  4511. * @param {string} postScriptName PDF specification full name for the font.
  4512. * @param {string} id PDF-document-instance-specific label assinged to the font.
  4513. * @param {string} fontStyle Style of the Font.
  4514. * @param {number | string} fontWeight Weight of the Font.
  4515. * @param {Object} encoding Encoding_name-to-Font_metrics_object mapping.
  4516. * @function
  4517. * @instance
  4518. * @memberof jsPDF#
  4519. * @name addFont
  4520. * @returns {string} fontId
  4521. */
  4522. API.addFont = function(
  4523. postScriptName,
  4524. fontName,
  4525. fontStyle,
  4526. fontWeight,
  4527. encoding
  4528. ) {
  4529. var encodingOptions = [
  4530. "StandardEncoding",
  4531. "MacRomanEncoding",
  4532. "Identity-H",
  4533. "WinAnsiEncoding"
  4534. ];
  4535. if (arguments[3] && encodingOptions.indexOf(arguments[3]) !== -1) {
  4536. //IE 11 fix
  4537. encoding = arguments[3];
  4538. } else if (arguments[3] && encodingOptions.indexOf(arguments[3]) == -1) {
  4539. fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight);
  4540. }
  4541. encoding = encoding || "Identity-H";
  4542. return addFont.call(this, postScriptName, fontName, fontStyle, encoding);
  4543. };
  4544. var lineWidth = options.lineWidth || 0.200025; // 2mm
  4545. /**
  4546. * Gets the line width, default: 0.200025.
  4547. *
  4548. * @function
  4549. * @instance
  4550. * @returns {number} lineWidth
  4551. * @memberof jsPDF#
  4552. * @name getLineWidth
  4553. */
  4554. var getLineWidth = (API.__private__.getLineWidth = API.getLineWidth = function() {
  4555. return lineWidth;
  4556. });
  4557. /**
  4558. * Sets line width for upcoming lines.
  4559. *
  4560. * @param {number} width Line width (in units declared at inception of PDF document).
  4561. * @function
  4562. * @instance
  4563. * @returns {jsPDF}
  4564. * @memberof jsPDF#
  4565. * @name setLineWidth
  4566. */
  4567. var setLineWidth = (API.__private__.setLineWidth = API.setLineWidth = function(
  4568. width
  4569. ) {
  4570. lineWidth = width;
  4571. out(hpf(scale(width)) + " w");
  4572. return this;
  4573. });
  4574. /**
  4575. * Sets the dash pattern for upcoming lines.
  4576. *
  4577. * To reset the settings simply call the method without any parameters.
  4578. * @param {Array<number>} dashArray An array containing 0-2 numbers. The first number sets the length of the
  4579. * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered
  4580. * to be as long as the dashes. An empty array means solid, unbroken lines.
  4581. * @param {number} dashPhase The phase lines start with.
  4582. * @function
  4583. * @instance
  4584. * @returns {jsPDF}
  4585. * @memberof jsPDF#
  4586. * @name setLineDashPattern
  4587. */
  4588. API.__private__.setLineDash = jsPDF.API.setLineDash = jsPDF.API.setLineDashPattern = function(
  4589. dashArray,
  4590. dashPhase
  4591. ) {
  4592. dashArray = dashArray || [];
  4593. dashPhase = dashPhase || 0;
  4594. if (isNaN(dashPhase) || !Array.isArray(dashArray)) {
  4595. throw new Error("Invalid arguments passed to jsPDF.setLineDash");
  4596. }
  4597. dashArray = dashArray
  4598. .map(function(x) {
  4599. return hpf(scale(x));
  4600. })
  4601. .join(" ");
  4602. dashPhase = hpf(scale(dashPhase));
  4603. out("[" + dashArray + "] " + dashPhase + " d");
  4604. return this;
  4605. };
  4606. var lineHeightFactor;
  4607. var getLineHeight = (API.__private__.getLineHeight = API.getLineHeight = function() {
  4608. return activeFontSize * lineHeightFactor;
  4609. });
  4610. API.__private__.getLineHeight = API.getLineHeight = function() {
  4611. return activeFontSize * lineHeightFactor;
  4612. };
  4613. /**
  4614. * Sets the LineHeightFactor of proportion.
  4615. *
  4616. * @param {number} value LineHeightFactor value. Default: 1.15.
  4617. * @function
  4618. * @instance
  4619. * @returns {jsPDF}
  4620. * @memberof jsPDF#
  4621. * @name setLineHeightFactor
  4622. */
  4623. var setLineHeightFactor = (API.__private__.setLineHeightFactor = API.setLineHeightFactor = function(
  4624. value
  4625. ) {
  4626. value = value || 1.15;
  4627. if (typeof value === "number") {
  4628. lineHeightFactor = value;
  4629. }
  4630. return this;
  4631. });
  4632. /**
  4633. * Gets the LineHeightFactor, default: 1.15.
  4634. *
  4635. * @function
  4636. * @instance
  4637. * @returns {number} lineHeightFactor
  4638. * @memberof jsPDF#
  4639. * @name getLineHeightFactor
  4640. */
  4641. var getLineHeightFactor = (API.__private__.getLineHeightFactor = API.getLineHeightFactor = function() {
  4642. return lineHeightFactor;
  4643. });
  4644. setLineHeightFactor(options.lineHeight);
  4645. var getHorizontalCoordinate = (API.__private__.getHorizontalCoordinate = function(
  4646. value
  4647. ) {
  4648. return scale(value);
  4649. });
  4650. var getVerticalCoordinate = (API.__private__.getVerticalCoordinate = function(
  4651. value
  4652. ) {
  4653. if (apiMode === ApiMode.ADVANCED) {
  4654. return value;
  4655. } else {
  4656. var pageHeight =
  4657. pagesContext[currentPage].mediaBox.topRightY -
  4658. pagesContext[currentPage].mediaBox.bottomLeftY;
  4659. return pageHeight - scale(value);
  4660. }
  4661. });
  4662. var getHorizontalCoordinateString = (API.__private__.getHorizontalCoordinateString = API.getHorizontalCoordinateString = function(
  4663. value
  4664. ) {
  4665. return hpf(getHorizontalCoordinate(value));
  4666. });
  4667. var getVerticalCoordinateString = (API.__private__.getVerticalCoordinateString = API.getVerticalCoordinateString = function(
  4668. value
  4669. ) {
  4670. return hpf(getVerticalCoordinate(value));
  4671. });
  4672. var strokeColor = options.strokeColor || "0 G";
  4673. /**
  4674. * Gets the stroke color for upcoming elements.
  4675. *
  4676. * @function
  4677. * @instance
  4678. * @returns {string} colorAsHex
  4679. * @memberof jsPDF#
  4680. * @name getDrawColor
  4681. */
  4682. API.__private__.getStrokeColor = API.getDrawColor = function() {
  4683. return decodeColorString(strokeColor);
  4684. };
  4685. /**
  4686. * Sets the stroke color for upcoming elements.
  4687. *
  4688. * Depending on the number of arguments given, Gray, RGB, or CMYK
  4689. * color space is implied.
  4690. *
  4691. * When only ch1 is given, "Gray" color space is implied and it
  4692. * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
  4693. * if values are communicated as String types, or in range from 0 (black)
  4694. * to 255 (white) if communicated as Number type.
  4695. * The RGB-like 0-255 range is provided for backward compatibility.
  4696. *
  4697. * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
  4698. * value must be in the range from 0.00 (minimum intensity) to to 1.00
  4699. * (max intensity) if values are communicated as String types, or
  4700. * from 0 (min intensity) to to 255 (max intensity) if values are communicated
  4701. * as Number types.
  4702. * The RGB-like 0-255 range is provided for backward compatibility.
  4703. *
  4704. * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
  4705. * value must be a in the range from 0.00 (0% concentration) to to
  4706. * 1.00 (100% concentration)
  4707. *
  4708. * Because JavaScript treats fixed point numbers badly (rounds to
  4709. * floating point nearest to binary representation) it is highly advised to
  4710. * communicate the fractional numbers as String types, not JavaScript Number type.
  4711. *
  4712. * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'.
  4713. * @param {Number} ch2 Color channel value.
  4714. * @param {Number} ch3 Color channel value.
  4715. * @param {Number} ch4 Color channel value.
  4716. *
  4717. * @function
  4718. * @instance
  4719. * @returns {jsPDF}
  4720. * @memberof jsPDF#
  4721. * @name setDrawColor
  4722. */
  4723. API.__private__.setStrokeColor = API.setDrawColor = function(
  4724. ch1,
  4725. ch2,
  4726. ch3,
  4727. ch4
  4728. ) {
  4729. var options = {
  4730. ch1: ch1,
  4731. ch2: ch2,
  4732. ch3: ch3,
  4733. ch4: ch4,
  4734. pdfColorType: "draw",
  4735. precision: 2
  4736. };
  4737. strokeColor = encodeColorString(options);
  4738. out(strokeColor);
  4739. return this;
  4740. };
  4741. var fillColor = options.fillColor || "0 g";
  4742. /**
  4743. * Gets the fill color for upcoming elements.
  4744. *
  4745. * @function
  4746. * @instance
  4747. * @returns {string} colorAsHex
  4748. * @memberof jsPDF#
  4749. * @name getFillColor
  4750. */
  4751. API.__private__.getFillColor = API.getFillColor = function() {
  4752. return decodeColorString(fillColor);
  4753. };
  4754. /**
  4755. * Sets the fill color for upcoming elements.
  4756. *
  4757. * Depending on the number of arguments given, Gray, RGB, or CMYK
  4758. * color space is implied.
  4759. *
  4760. * When only ch1 is given, "Gray" color space is implied and it
  4761. * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
  4762. * if values are communicated as String types, or in range from 0 (black)
  4763. * to 255 (white) if communicated as Number type.
  4764. * The RGB-like 0-255 range is provided for backward compatibility.
  4765. *
  4766. * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
  4767. * value must be in the range from 0.00 (minimum intensity) to to 1.00
  4768. * (max intensity) if values are communicated as String types, or
  4769. * from 0 (min intensity) to to 255 (max intensity) if values are communicated
  4770. * as Number types.
  4771. * The RGB-like 0-255 range is provided for backward compatibility.
  4772. *
  4773. * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
  4774. * value must be a in the range from 0.00 (0% concentration) to to
  4775. * 1.00 (100% concentration)
  4776. *
  4777. * Because JavaScript treats fixed point numbers badly (rounds to
  4778. * floating point nearest to binary representation) it is highly advised to
  4779. * communicate the fractional numbers as String types, not JavaScript Number type.
  4780. *
  4781. * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'.
  4782. * @param {Number} ch2 Color channel value.
  4783. * @param {Number} ch3 Color channel value.
  4784. * @param {Number} ch4 Color channel value.
  4785. *
  4786. * @function
  4787. * @instance
  4788. * @returns {jsPDF}
  4789. * @memberof jsPDF#
  4790. * @name setFillColor
  4791. */
  4792. API.__private__.setFillColor = API.setFillColor = function(
  4793. ch1,
  4794. ch2,
  4795. ch3,
  4796. ch4
  4797. ) {
  4798. var options = {
  4799. ch1: ch1,
  4800. ch2: ch2,
  4801. ch3: ch3,
  4802. ch4: ch4,
  4803. pdfColorType: "fill",
  4804. precision: 2
  4805. };
  4806. fillColor = encodeColorString(options);
  4807. out(fillColor);
  4808. return this;
  4809. };
  4810. var textColor = options.textColor || "0 g";
  4811. /**
  4812. * Gets the text color for upcoming elements.
  4813. *
  4814. * @function
  4815. * @instance
  4816. * @returns {string} colorAsHex
  4817. * @memberof jsPDF#
  4818. * @name getTextColor
  4819. */
  4820. var getTextColor = (API.__private__.getTextColor = API.getTextColor = function() {
  4821. return decodeColorString(textColor);
  4822. });
  4823. /**
  4824. * Sets the text color for upcoming elements.
  4825. *
  4826. * Depending on the number of arguments given, Gray, RGB, or CMYK
  4827. * color space is implied.
  4828. *
  4829. * When only ch1 is given, "Gray" color space is implied and it
  4830. * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
  4831. * if values are communicated as String types, or in range from 0 (black)
  4832. * to 255 (white) if communicated as Number type.
  4833. * The RGB-like 0-255 range is provided for backward compatibility.
  4834. *
  4835. * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
  4836. * value must be in the range from 0.00 (minimum intensity) to to 1.00
  4837. * (max intensity) if values are communicated as String types, or
  4838. * from 0 (min intensity) to to 255 (max intensity) if values are communicated
  4839. * as Number types.
  4840. * The RGB-like 0-255 range is provided for backward compatibility.
  4841. *
  4842. * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
  4843. * value must be a in the range from 0.00 (0% concentration) to to
  4844. * 1.00 (100% concentration)
  4845. *
  4846. * Because JavaScript treats fixed point numbers badly (rounds to
  4847. * floating point nearest to binary representation) it is highly advised to
  4848. * communicate the fractional numbers as String types, not JavaScript Number type.
  4849. *
  4850. * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'.
  4851. * @param {Number} ch2 Color channel value.
  4852. * @param {Number} ch3 Color channel value.
  4853. * @param {Number} ch4 Color channel value.
  4854. *
  4855. * @function
  4856. * @instance
  4857. * @returns {jsPDF}
  4858. * @memberof jsPDF#
  4859. * @name setTextColor
  4860. */
  4861. API.__private__.setTextColor = API.setTextColor = function(
  4862. ch1,
  4863. ch2,
  4864. ch3,
  4865. ch4
  4866. ) {
  4867. var options = {
  4868. ch1: ch1,
  4869. ch2: ch2,
  4870. ch3: ch3,
  4871. ch4: ch4,
  4872. pdfColorType: "text",
  4873. precision: 3
  4874. };
  4875. textColor = encodeColorString(options);
  4876. return this;
  4877. };
  4878. var activeCharSpace = options.charSpace;
  4879. /**
  4880. * Get global value of CharSpace.
  4881. *
  4882. * @function
  4883. * @instance
  4884. * @returns {number} charSpace
  4885. * @memberof jsPDF#
  4886. * @name getCharSpace
  4887. */
  4888. var getCharSpace = (API.__private__.getCharSpace = API.getCharSpace = function() {
  4889. return parseFloat(activeCharSpace || 0);
  4890. });
  4891. /**
  4892. * Set global value of CharSpace.
  4893. *
  4894. * @param {number} charSpace
  4895. * @function
  4896. * @instance
  4897. * @returns {jsPDF} jsPDF-instance
  4898. * @memberof jsPDF#
  4899. * @name setCharSpace
  4900. */
  4901. API.__private__.setCharSpace = API.setCharSpace = function(charSpace) {
  4902. if (isNaN(charSpace)) {
  4903. throw new Error("Invalid argument passed to jsPDF.setCharSpace");
  4904. }
  4905. activeCharSpace = charSpace;
  4906. return this;
  4907. };
  4908. var lineCapID = 0;
  4909. /**
  4910. * Is an Object providing a mapping from human-readable to
  4911. * integer flag values designating the varieties of line cap
  4912. * and join styles.
  4913. *
  4914. * @memberof jsPDF#
  4915. * @name CapJoinStyles
  4916. */
  4917. API.CapJoinStyles = {
  4918. 0: 0,
  4919. butt: 0,
  4920. but: 0,
  4921. miter: 0,
  4922. 1: 1,
  4923. round: 1,
  4924. rounded: 1,
  4925. circle: 1,
  4926. 2: 2,
  4927. projecting: 2,
  4928. project: 2,
  4929. square: 2,
  4930. bevel: 2
  4931. };
  4932. /**
  4933. * Sets the line cap styles.
  4934. * See {jsPDF.CapJoinStyles} for variants.
  4935. *
  4936. * @param {String|Number} style A string or number identifying the type of line cap.
  4937. * @function
  4938. * @instance
  4939. * @returns {jsPDF}
  4940. * @memberof jsPDF#
  4941. * @name setLineCap
  4942. */
  4943. API.__private__.setLineCap = API.setLineCap = function(style) {
  4944. var id = API.CapJoinStyles[style];
  4945. if (id === undefined) {
  4946. throw new Error(
  4947. "Line cap style of '" +
  4948. style +
  4949. "' is not recognized. See or extend .CapJoinStyles property for valid styles"
  4950. );
  4951. }
  4952. lineCapID = id;
  4953. out(id + " J");
  4954. return this;
  4955. };
  4956. var lineJoinID = 0;
  4957. /**
  4958. * Sets the line join styles.
  4959. * See {jsPDF.CapJoinStyles} for variants.
  4960. *
  4961. * @param {String|Number} style A string or number identifying the type of line join.
  4962. * @function
  4963. * @instance
  4964. * @returns {jsPDF}
  4965. * @memberof jsPDF#
  4966. * @name setLineJoin
  4967. */
  4968. API.__private__.setLineJoin = API.setLineJoin = function(style) {
  4969. var id = API.CapJoinStyles[style];
  4970. if (id === undefined) {
  4971. throw new Error(
  4972. "Line join style of '" +
  4973. style +
  4974. "' is not recognized. See or extend .CapJoinStyles property for valid styles"
  4975. );
  4976. }
  4977. lineJoinID = id;
  4978. out(id + " j");
  4979. return this;
  4980. };
  4981. var miterLimit;
  4982. /**
  4983. * Sets the miterLimit property, which effects the maximum miter length.
  4984. *
  4985. * @param {number} length The length of the miter
  4986. * @function
  4987. * @instance
  4988. * @returns {jsPDF}
  4989. * @memberof jsPDF#
  4990. * @name setLineMiterLimit
  4991. */
  4992. API.__private__.setLineMiterLimit = API.__private__.setMiterLimit = API.setLineMiterLimit = API.setMiterLimit = function(
  4993. length
  4994. ) {
  4995. length = length || 0;
  4996. if (isNaN(length)) {
  4997. throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit");
  4998. }
  4999. out(hpf(scale(length)) + " M");
  5000. return this;
  5001. };
  5002. /**
  5003. * An object representing a pdf graphics state.
  5004. * @class GState
  5005. */
  5006. /**
  5007. *
  5008. * @param parameters A parameter object that contains all properties this graphics state wants to set.
  5009. * Supported are: opacity, stroke-opacity
  5010. * @constructor
  5011. */
  5012. API.GState = GState;
  5013. /**
  5014. * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}.
  5015. * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState
  5016. * it will be added before use.
  5017. * @function
  5018. * @returns {jsPDF}
  5019. * @memberof jsPDF#
  5020. * @name setGState
  5021. */
  5022. API.setGState = function(gState) {
  5023. if (typeof gState === "string") {
  5024. gState = gStates[gStatesMap[gState]];
  5025. } else {
  5026. gState = addGState(null, gState);
  5027. }
  5028. if (!gState.equals(activeGState)) {
  5029. out("/" + gState.id + " gs");
  5030. activeGState = gState;
  5031. }
  5032. };
  5033. /**
  5034. * Adds a new Graphics State. Duplicates are automatically eliminated.
  5035. * @param {String} key Might also be null, if no later reference to this gState is needed
  5036. * @param {Object} gState The gState object
  5037. */
  5038. var addGState = function(key, gState) {
  5039. // only add it if it is not already present (the keys provided by the user must be unique!)
  5040. if (key && gStatesMap[key]) return;
  5041. var duplicate = false;
  5042. for (var s in gStates) {
  5043. if (gStates.hasOwnProperty(s)) {
  5044. if (gStates[s].equals(gState)) {
  5045. duplicate = true;
  5046. break;
  5047. }
  5048. }
  5049. }
  5050. if (duplicate) {
  5051. gState = gStates[s];
  5052. } else {
  5053. var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10);
  5054. gStates[gStateKey] = gState;
  5055. gState.id = gStateKey;
  5056. }
  5057. // several user keys may point to the same GState object
  5058. key && (gStatesMap[key] = gState.id);
  5059. events.publish("addGState", gState);
  5060. return gState;
  5061. };
  5062. /**
  5063. * Adds a new {@link GState} for later use. See {@link setGState}.
  5064. * @param {String} key
  5065. * @param {GState} gState
  5066. * @function
  5067. * @instance
  5068. * @returns {jsPDF}
  5069. *
  5070. * @memberof jsPDF#
  5071. * @name addGState
  5072. */
  5073. API.addGState = function(key, gState) {
  5074. addGState(key, gState);
  5075. return this;
  5076. };
  5077. /**
  5078. * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState}
  5079. * later. Here, the general pdf graphics state is meant, also including the current transformation matrix,
  5080. * fill and stroke colors etc.
  5081. * @function
  5082. * @returns {jsPDF}
  5083. * @memberof jsPDF#
  5084. * @name saveGraphicsState
  5085. */
  5086. API.saveGraphicsState = function() {
  5087. out("q");
  5088. // as we cannot set font key and size independently we must keep track of both
  5089. fontStateStack.push({
  5090. key: activeFontKey,
  5091. size: activeFontSize,
  5092. color: textColor
  5093. });
  5094. return this;
  5095. };
  5096. /**
  5097. * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack").
  5098. * @function
  5099. * @returns {jsPDF}
  5100. * @memberof jsPDF#
  5101. * @name restoreGraphicsState
  5102. */
  5103. API.restoreGraphicsState = function() {
  5104. out("Q");
  5105. // restore previous font state
  5106. var fontState = fontStateStack.pop();
  5107. activeFontKey = fontState.key;
  5108. activeFontSize = fontState.size;
  5109. textColor = fontState.color;
  5110. activeGState = null;
  5111. return this;
  5112. };
  5113. /**
  5114. * Appends this matrix to the left of all previously applied matrices.
  5115. *
  5116. * @param {Matrix} matrix
  5117. * @function
  5118. * @returns {jsPDF}
  5119. * @memberof jsPDF#
  5120. * @name setCurrentTransformationMatrix
  5121. */
  5122. API.setCurrentTransformationMatrix = function(matrix) {
  5123. out(matrix.toString() + " cm");
  5124. return this;
  5125. };
  5126. /**
  5127. * Inserts a debug comment into the generated pdf.
  5128. * @function
  5129. * @instance
  5130. * @param {String} text
  5131. * @returns {jsPDF}
  5132. * @memberof jsPDF#
  5133. * @name comment
  5134. */
  5135. API.comment = function(text) {
  5136. out("#" + text);
  5137. return this;
  5138. };
  5139. /**
  5140. * Point
  5141. */
  5142. var Point = function(x, y) {
  5143. var _x = x || 0;
  5144. Object.defineProperty(this, "x", {
  5145. enumerable: true,
  5146. get: function() {
  5147. return _x;
  5148. },
  5149. set: function(value) {
  5150. if (!isNaN(value)) {
  5151. _x = parseFloat(value);
  5152. }
  5153. }
  5154. });
  5155. var _y = y || 0;
  5156. Object.defineProperty(this, "y", {
  5157. enumerable: true,
  5158. get: function() {
  5159. return _y;
  5160. },
  5161. set: function(value) {
  5162. if (!isNaN(value)) {
  5163. _y = parseFloat(value);
  5164. }
  5165. }
  5166. });
  5167. var _type = "pt";
  5168. Object.defineProperty(this, "type", {
  5169. enumerable: true,
  5170. get: function() {
  5171. return _type;
  5172. },
  5173. set: function(value) {
  5174. _type = value.toString();
  5175. }
  5176. });
  5177. return this;
  5178. };
  5179. /**
  5180. * Rectangle
  5181. */
  5182. var Rectangle = function(x, y, w, h) {
  5183. Point.call(this, x, y);
  5184. this.type = "rect";
  5185. var _w = w || 0;
  5186. Object.defineProperty(this, "w", {
  5187. enumerable: true,
  5188. get: function() {
  5189. return _w;
  5190. },
  5191. set: function(value) {
  5192. if (!isNaN(value)) {
  5193. _w = parseFloat(value);
  5194. }
  5195. }
  5196. });
  5197. var _h = h || 0;
  5198. Object.defineProperty(this, "h", {
  5199. enumerable: true,
  5200. get: function() {
  5201. return _h;
  5202. },
  5203. set: function(value) {
  5204. if (!isNaN(value)) {
  5205. _h = parseFloat(value);
  5206. }
  5207. }
  5208. });
  5209. return this;
  5210. };
  5211. /**
  5212. * FormObject/RenderTarget
  5213. */
  5214. var RenderTarget = function() {
  5215. this.page = page;
  5216. this.currentPage = currentPage;
  5217. this.pages = pages.slice(0);
  5218. this.pagesContext = pagesContext.slice(0);
  5219. this.x = pageX;
  5220. this.y = pageY;
  5221. this.matrix = pageMatrix;
  5222. this.width = getPageWidth(currentPage);
  5223. this.height = getPageHeight(currentPage);
  5224. this.outputDestination = outputDestination;
  5225. this.id = ""; // set by endFormObject()
  5226. this.objectNumber = -1; // will be set by putXObject()
  5227. };
  5228. RenderTarget.prototype.restore = function() {
  5229. page = this.page;
  5230. currentPage = this.currentPage;
  5231. pagesContext = this.pagesContext;
  5232. pages = this.pages;
  5233. pageX = this.x;
  5234. pageY = this.y;
  5235. pageMatrix = this.matrix;
  5236. setPageWidth(currentPage, this.width);
  5237. setPageHeight(currentPage, this.height);
  5238. outputDestination = this.outputDestination;
  5239. };
  5240. var beginNewRenderTarget = function(x, y, width, height, matrix) {
  5241. // save current state
  5242. renderTargetStack.push(new RenderTarget());
  5243. // clear pages
  5244. page = currentPage = 0;
  5245. pages = [];
  5246. pageX = x;
  5247. pageY = y;
  5248. pageMatrix = matrix;
  5249. beginPage([width, height]);
  5250. };
  5251. var endFormObject = function(key) {
  5252. // only add it if it is not already present (the keys provided by the user must be unique!)
  5253. if (renderTargetMap[key]) {
  5254. renderTargetStack.pop().restore();
  5255. return;
  5256. }
  5257. // save the created xObject
  5258. var newXObject = new RenderTarget();
  5259. var xObjectId = "Xo" + (Object.keys(renderTargets).length + 1).toString(10);
  5260. newXObject.id = xObjectId;
  5261. renderTargetMap[key] = xObjectId;
  5262. renderTargets[xObjectId] = newXObject;
  5263. events.publish("addFormObject", newXObject);
  5264. // restore state from stack
  5265. renderTargetStack.pop().restore();
  5266. };
  5267. /**
  5268. * Starts a new pdf form object, which means that all consequent draw calls target a new independent object
  5269. * until {@link endFormObject} is called. The created object can be referenced and drawn later using
  5270. * {@link doFormObject}. Nested form objects are possible.
  5271. * x, y, width, height set the bounding box that is used to clip the content.
  5272. *
  5273. * @param {number} x
  5274. * @param {number} y
  5275. * @param {number} width
  5276. * @param {number} height
  5277. * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to
  5278. * the parent's.
  5279. * @function
  5280. * @returns {jsPDF}
  5281. * @memberof jsPDF#
  5282. * @name beginFormObject
  5283. */
  5284. API.beginFormObject = function(x, y, width, height, matrix) {
  5285. // The user can set the output target to a new form object. Nested form objects are possible.
  5286. // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as
  5287. // the PDF-Spec states:
  5288. // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which
  5289. // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions,
  5290. // form XObjects may be independent of the content streams in which they appear, and this is strongly
  5291. // recommended although not required"
  5292. beginNewRenderTarget(x, y, width, height, matrix);
  5293. return this;
  5294. };
  5295. /**
  5296. * Completes and saves the form object.
  5297. * @param {String} key The key by which this form object can be referenced.
  5298. * @function
  5299. * @returns {jsPDF}
  5300. * @memberof jsPDF#
  5301. * @name endFormObject
  5302. */
  5303. API.endFormObject = function(key) {
  5304. endFormObject(key);
  5305. return this;
  5306. };
  5307. /**
  5308. * Draws the specified form object by referencing to the respective pdf XObject created with
  5309. * {@link API.beginFormObject} and {@link endFormObject}.
  5310. * The location is determined by matrix.
  5311. *
  5312. * @param {String} key The key to the form object.
  5313. * @param {Matrix} matrix The matrix applied before drawing the form object.
  5314. * @function
  5315. * @returns {jsPDF}
  5316. * @memberof jsPDF#
  5317. * @name doFormObject
  5318. */
  5319. API.doFormObject = function(key, matrix) {
  5320. var xObject = renderTargets[renderTargetMap[key]];
  5321. out("q");
  5322. out(matrix.toString() + " cm");
  5323. out("/" + xObject.id + " Do");
  5324. out("Q");
  5325. return this;
  5326. };
  5327. /**
  5328. * Returns the form object specified by key.
  5329. * @param key {String}
  5330. * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}}
  5331. * @function
  5332. * @returns {jsPDF}
  5333. * @memberof jsPDF#
  5334. * @name getFormObject
  5335. */
  5336. API.getFormObject = function(key) {
  5337. var xObject = renderTargets[renderTargetMap[key]];
  5338. return {
  5339. x: xObject.x,
  5340. y: xObject.y,
  5341. width: xObject.width,
  5342. height: xObject.height,
  5343. matrix: xObject.matrix
  5344. };
  5345. };
  5346. /**
  5347. * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf').
  5348. * Uses FileSaver.js-method saveAs.
  5349. *
  5350. * @memberof jsPDF#
  5351. * @name save
  5352. * @function
  5353. * @instance
  5354. * @param {string} filename The filename including extension.
  5355. * @param {Object} options An Object with additional options, possible options: 'returnPromise'.
  5356. * @returns {jsPDF|Promise} jsPDF-instance */
  5357. API.save = function(filename, options) {
  5358. filename = filename || "generated.pdf";
  5359. options = options || {};
  5360. options.returnPromise = options.returnPromise || false;
  5361. // @if MODULE_FORMAT!='cjs'
  5362. if (options.returnPromise === false) {
  5363. saveAs(getBlob(buildDocument()), filename);
  5364. if (typeof saveAs.unload === "function") {
  5365. if (globalObject.setTimeout) {
  5366. setTimeout(saveAs.unload, 911);
  5367. }
  5368. }
  5369. return this;
  5370. } else {
  5371. return new Promise(function(resolve, reject) {
  5372. try {
  5373. var result = saveAs(getBlob(buildDocument()), filename);
  5374. if (typeof saveAs.unload === "function") {
  5375. if (globalObject.setTimeout) {
  5376. setTimeout(saveAs.unload, 911);
  5377. }
  5378. }
  5379. resolve(result);
  5380. } catch (e) {
  5381. reject(e.message);
  5382. }
  5383. });
  5384. }
  5385. // @endif
  5386. // @if MODULE_FORMAT='cjs'
  5387. // eslint-disable-next-line no-unreachable
  5388. var fs = require("fs");
  5389. var buffer = Buffer.from(getArrayBuffer(buildDocument()));
  5390. if (options.returnPromise === false) {
  5391. fs.writeFileSync(filename, buffer);
  5392. } else {
  5393. return new Promise(function(resolve, reject) {
  5394. fs.writeFile(filename, buffer, function(err) {
  5395. if (err) {
  5396. reject(err);
  5397. } else {
  5398. resolve();
  5399. }
  5400. });
  5401. });
  5402. }
  5403. // @endif
  5404. };
  5405. // applying plugins (more methods) ON TOP of built-in API.
  5406. // this is intentional as we allow plugins to override
  5407. // built-ins
  5408. for (var plugin in jsPDF.API) {
  5409. if (jsPDF.API.hasOwnProperty(plugin)) {
  5410. if (plugin === "events" && jsPDF.API.events.length) {
  5411. (function(events, newEvents) {
  5412. // jsPDF.API.events is a JS Array of Arrays
  5413. // where each Array is a pair of event name, handler
  5414. // Events were added by plugins to the jsPDF instantiator.
  5415. // These are always added to the new instance and some ran
  5416. // during instantiation.
  5417. var eventname, handler_and_args, i;
  5418. for (i = newEvents.length - 1; i !== -1; i--) {
  5419. // subscribe takes 3 args: 'topic', function, runonce_flag
  5420. // if undefined, runonce is false.
  5421. // users can attach callback directly,
  5422. // or they can attach an array with [callback, runonce_flag]
  5423. // that's what the "apply" magic is for below.
  5424. eventname = newEvents[i][0];
  5425. handler_and_args = newEvents[i][1];
  5426. events.subscribe.apply(
  5427. events,
  5428. [eventname].concat(
  5429. typeof handler_and_args === "function"
  5430. ? [handler_and_args]
  5431. : handler_and_args
  5432. )
  5433. );
  5434. }
  5435. })(events, jsPDF.API.events);
  5436. } else {
  5437. API[plugin] = jsPDF.API[plugin];
  5438. }
  5439. }
  5440. }
  5441. var getPageWidth = (API.getPageWidth = function(pageNumber) {
  5442. pageNumber = pageNumber || currentPage;
  5443. return (
  5444. (pagesContext[pageNumber].mediaBox.topRightX -
  5445. pagesContext[pageNumber].mediaBox.bottomLeftX) /
  5446. scaleFactor
  5447. );
  5448. });
  5449. var setPageWidth = (API.setPageWidth = function(pageNumber, value) {
  5450. pagesContext[pageNumber].mediaBox.topRightX =
  5451. value * scaleFactor + pagesContext[pageNumber].mediaBox.bottomLeftX;
  5452. });
  5453. var getPageHeight = (API.getPageHeight = function(pageNumber) {
  5454. pageNumber = pageNumber || currentPage;
  5455. return (
  5456. (pagesContext[pageNumber].mediaBox.topRightY -
  5457. pagesContext[pageNumber].mediaBox.bottomLeftY) /
  5458. scaleFactor
  5459. );
  5460. });
  5461. var setPageHeight = (API.setPageHeight = function(pageNumber, value) {
  5462. pagesContext[pageNumber].mediaBox.topRightY =
  5463. value * scaleFactor + pagesContext[pageNumber].mediaBox.bottomLeftY;
  5464. });
  5465. /**
  5466. * Object exposing internal API to plugins
  5467. * @public
  5468. * @ignore
  5469. */
  5470. API.internal = {
  5471. pdfEscape: pdfEscape,
  5472. getStyle: getStyle,
  5473. getFont: getFontEntry,
  5474. getFontSize: getFontSize,
  5475. getCharSpace: getCharSpace,
  5476. getTextColor: getTextColor,
  5477. getLineHeight: getLineHeight,
  5478. getLineHeightFactor: getLineHeightFactor,
  5479. getLineWidth: getLineWidth,
  5480. write: write,
  5481. getHorizontalCoordinate: getHorizontalCoordinate,
  5482. getVerticalCoordinate: getVerticalCoordinate,
  5483. getCoordinateString: getHorizontalCoordinateString,
  5484. getVerticalCoordinateString: getVerticalCoordinateString,
  5485. collections: {},
  5486. newObject: newObject,
  5487. newAdditionalObject: newAdditionalObject,
  5488. newObjectDeferred: newObjectDeferred,
  5489. newObjectDeferredBegin: newObjectDeferredBegin,
  5490. getFilters: getFilters,
  5491. putStream: putStream,
  5492. events: events,
  5493. scaleFactor: scaleFactor,
  5494. pageSize: {
  5495. getWidth: function() {
  5496. return getPageWidth(currentPage);
  5497. },
  5498. setWidth: function(value) {
  5499. setPageWidth(currentPage, value);
  5500. },
  5501. getHeight: function() {
  5502. return getPageHeight(currentPage);
  5503. },
  5504. setHeight: function(value) {
  5505. setPageHeight(currentPage, value);
  5506. }
  5507. },
  5508. encryptionOptions: encryptionOptions,
  5509. encryption: encryption,
  5510. getEncryptor: getEncryptor,
  5511. output: output,
  5512. getNumberOfPages: getNumberOfPages,
  5513. pages: pages,
  5514. out: out,
  5515. f2: f2,
  5516. f3: f3,
  5517. getPageInfo: getPageInfo,
  5518. getPageInfoByObjId: getPageInfoByObjId,
  5519. getCurrentPageInfo: getCurrentPageInfo,
  5520. getPDFVersion: getPdfVersion,
  5521. Point: Point,
  5522. Rectangle: Rectangle,
  5523. Matrix: Matrix,
  5524. hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them.
  5525. };
  5526. Object.defineProperty(API.internal.pageSize, "width", {
  5527. get: function() {
  5528. return getPageWidth(currentPage);
  5529. },
  5530. set: function(value) {
  5531. setPageWidth(currentPage, value);
  5532. },
  5533. enumerable: true,
  5534. configurable: true
  5535. });
  5536. Object.defineProperty(API.internal.pageSize, "height", {
  5537. get: function() {
  5538. return getPageHeight(currentPage);
  5539. },
  5540. set: function(value) {
  5541. setPageHeight(currentPage, value);
  5542. },
  5543. enumerable: true,
  5544. configurable: true
  5545. });
  5546. //////////////////////////////////////////////////////
  5547. // continuing initialization of jsPDF Document object
  5548. //////////////////////////////////////////////////////
  5549. // Add the first page automatically
  5550. addFonts.call(API, standardFonts);
  5551. activeFontKey = "F1";
  5552. _addPage(format, orientation);
  5553. events.publish("initialized");
  5554. return API;
  5555. }
  5556. /**
  5557. * jsPDF.API is a STATIC property of jsPDF class.
  5558. * jsPDF.API is an object you can add methods and properties to.
  5559. * The methods / properties you add will show up in new jsPDF objects.
  5560. *
  5561. * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics,
  5562. * callbacks to this object. These will be reassigned to all new instances of jsPDF.
  5563. *
  5564. * @static
  5565. * @public
  5566. * @memberof jsPDF#
  5567. * @name API
  5568. *
  5569. * @example
  5570. * jsPDF.API.mymethod = function(){
  5571. * // 'this' will be ref to internal API object. see jsPDF source
  5572. * // , so you can refer to built-in methods like so:
  5573. * // this.line(....)
  5574. * // this.text(....)
  5575. * }
  5576. * var pdfdoc = new jsPDF()
  5577. * pdfdoc.mymethod() // <- !!!!!!
  5578. */
  5579. jsPDF.API = {
  5580. events: []
  5581. };
  5582. /**
  5583. * The version of jsPDF.
  5584. * @name version
  5585. * @type {string}
  5586. * @memberof jsPDF#
  5587. */
  5588. jsPDF.version = "0.0.0";
  5589. export { jsPDF, ShadingPattern, TilingPattern, GState };
  5590. export default jsPDF;