modules/outline.js

  1. /**
  2. * @license
  3. * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
  4. *
  5. * Licensed under the MIT License.
  6. * http://opensource.org/licenses/mit-license
  7. */
  8. import { jsPDF } from "../jspdf.js";
  9. /**
  10. * jsPDF Outline PlugIn
  11. *
  12. * Generates a PDF Outline
  13. * @name outline
  14. * @module
  15. */
  16. (function(jsPDFAPI) {
  17. "use strict";
  18. var namesOid;
  19. //var destsGoto = [];
  20. jsPDFAPI.events.push([
  21. "postPutResources",
  22. function() {
  23. var pdf = this;
  24. var rx = /^(\d+) 0 obj$/;
  25. // Write action goto objects for each page
  26. // this.outline.destsGoto = [];
  27. // for (var i = 0; i < totalPages; i++) {
  28. // var id = pdf.internal.newObject();
  29. // this.outline.destsGoto.push(id);
  30. // pdf.internal.write("<</D[" + (i * 2 + 3) + " 0 R /XYZ null
  31. // null null]/S/GoTo>> endobj");
  32. // }
  33. //
  34. // for (var i = 0; i < dests.length; i++) {
  35. // pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0
  36. // R");
  37. // }
  38. //
  39. if (this.outline.root.children.length > 0) {
  40. var lines = pdf.outline.render().split(/\r\n/);
  41. for (var i = 0; i < lines.length; i++) {
  42. var line = lines[i];
  43. var m = rx.exec(line);
  44. if (m != null) {
  45. var oid = m[1];
  46. pdf.internal.newObjectDeferredBegin(oid, false);
  47. }
  48. pdf.internal.write(line);
  49. }
  50. }
  51. // This code will write named destination for each page reference
  52. // (page_1, etc)
  53. if (this.outline.createNamedDestinations) {
  54. var totalPages = this.internal.pages.length;
  55. // WARNING: this assumes jsPDF starts on page 3 and pageIDs
  56. // follow 5, 7, 9, etc
  57. // Write destination objects for each page
  58. var dests = [];
  59. for (var i = 0; i < totalPages; i++) {
  60. var id = pdf.internal.newObject();
  61. dests.push(id);
  62. var info = pdf.internal.getPageInfo(i + 1);
  63. pdf.internal.write(
  64. "<< /D[" + info.objId + " 0 R /XYZ null null null]>> endobj"
  65. );
  66. }
  67. // assign a name for each destination
  68. var names2Oid = pdf.internal.newObject();
  69. pdf.internal.write("<< /Names [ ");
  70. for (var i = 0; i < dests.length; i++) {
  71. pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 R");
  72. }
  73. pdf.internal.write(" ] >>", "endobj");
  74. // var kids = pdf.internal.newObject();
  75. // pdf.internal.write('<< /Kids [ ' + names2Oid + ' 0 R');
  76. // pdf.internal.write(' ] >>', 'endobj');
  77. namesOid = pdf.internal.newObject();
  78. pdf.internal.write("<< /Dests " + names2Oid + " 0 R");
  79. pdf.internal.write(">>", "endobj");
  80. }
  81. }
  82. ]);
  83. jsPDFAPI.events.push([
  84. "putCatalog",
  85. function() {
  86. var pdf = this;
  87. if (pdf.outline.root.children.length > 0) {
  88. pdf.internal.write(
  89. "/Outlines",
  90. this.outline.makeRef(this.outline.root)
  91. );
  92. if (this.outline.createNamedDestinations) {
  93. pdf.internal.write("/Names " + namesOid + " 0 R");
  94. }
  95. // Open with Bookmarks showing
  96. // pdf.internal.write("/PageMode /UseOutlines");
  97. }
  98. }
  99. ]);
  100. jsPDFAPI.events.push([
  101. "initialized",
  102. function() {
  103. var pdf = this;
  104. pdf.outline = {
  105. createNamedDestinations: false,
  106. root: {
  107. children: []
  108. }
  109. };
  110. /**
  111. * Options: pageNumber
  112. */
  113. pdf.outline.add = function(parent, title, options) {
  114. var item = {
  115. title: title,
  116. options: options,
  117. children: []
  118. };
  119. if (parent == null) {
  120. parent = this.root;
  121. }
  122. parent.children.push(item);
  123. return item;
  124. };
  125. pdf.outline.render = function() {
  126. this.ctx = {};
  127. this.ctx.val = "";
  128. this.ctx.pdf = pdf;
  129. this.genIds_r(this.root);
  130. this.renderRoot(this.root);
  131. this.renderItems(this.root);
  132. return this.ctx.val;
  133. };
  134. pdf.outline.genIds_r = function(node) {
  135. node.id = pdf.internal.newObjectDeferred();
  136. for (var i = 0; i < node.children.length; i++) {
  137. this.genIds_r(node.children[i]);
  138. }
  139. };
  140. pdf.outline.renderRoot = function(node) {
  141. this.objStart(node);
  142. this.line("/Type /Outlines");
  143. if (node.children.length > 0) {
  144. this.line("/First " + this.makeRef(node.children[0]));
  145. this.line(
  146. "/Last " + this.makeRef(node.children[node.children.length - 1])
  147. );
  148. }
  149. this.line(
  150. "/Count " +
  151. this.count_r(
  152. {
  153. count: 0
  154. },
  155. node
  156. )
  157. );
  158. this.objEnd();
  159. };
  160. pdf.outline.renderItems = function(node) {
  161. var getVerticalCoordinateString = this.ctx.pdf.internal
  162. .getVerticalCoordinateString;
  163. for (var i = 0; i < node.children.length; i++) {
  164. var item = node.children[i];
  165. this.objStart(item);
  166. this.line("/Title " + this.makeString(item.title));
  167. this.line("/Parent " + this.makeRef(node));
  168. if (i > 0) {
  169. this.line("/Prev " + this.makeRef(node.children[i - 1]));
  170. }
  171. if (i < node.children.length - 1) {
  172. this.line("/Next " + this.makeRef(node.children[i + 1]));
  173. }
  174. if (item.children.length > 0) {
  175. this.line("/First " + this.makeRef(item.children[0]));
  176. this.line(
  177. "/Last " + this.makeRef(item.children[item.children.length - 1])
  178. );
  179. }
  180. var count = (this.count = this.count_r(
  181. {
  182. count: 0
  183. },
  184. item
  185. ));
  186. if (count > 0) {
  187. this.line("/Count " + count);
  188. }
  189. if (item.options) {
  190. if (item.options.pageNumber) {
  191. // Explicit Destination
  192. //WARNING this assumes page ids are 3,5,7, etc.
  193. var info = pdf.internal.getPageInfo(item.options.pageNumber);
  194. this.line(
  195. "/Dest " +
  196. "[" +
  197. info.objId +
  198. " 0 R /XYZ 0 " +
  199. getVerticalCoordinateString(0) +
  200. " 0]"
  201. );
  202. // this line does not work on all clients (pageNumber instead of page ref)
  203. //this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.getHeight() + ' 0]');
  204. // Named Destination
  205. // this.line('/Dest (page_' + (item.options.pageNumber) + ')');
  206. // Action Destination
  207. // var id = pdf.internal.newObject();
  208. // pdf.internal.write('<</D[' + (item.options.pageNumber - 1) + ' /XYZ null null null]/S/GoTo>> endobj');
  209. // this.line('/A ' + id + ' 0 R' );
  210. }
  211. }
  212. this.objEnd();
  213. }
  214. for (var z = 0; z < node.children.length; z++) {
  215. this.renderItems(node.children[z]);
  216. }
  217. };
  218. pdf.outline.line = function(text) {
  219. this.ctx.val += text + "\r\n";
  220. };
  221. pdf.outline.makeRef = function(node) {
  222. return node.id + " 0 R";
  223. };
  224. pdf.outline.makeString = function(val) {
  225. return "(" + pdf.internal.pdfEscape(val) + ")";
  226. };
  227. pdf.outline.objStart = function(node) {
  228. this.ctx.val += "\r\n" + node.id + " 0 obj" + "\r\n<<\r\n";
  229. };
  230. pdf.outline.objEnd = function() {
  231. this.ctx.val += ">> \r\n" + "endobj" + "\r\n";
  232. };
  233. pdf.outline.count_r = function(ctx, node) {
  234. for (var i = 0; i < node.children.length; i++) {
  235. ctx.count++;
  236. this.count_r(ctx, node.children[i]);
  237. }
  238. return ctx.count;
  239. };
  240. }
  241. ]);
  242. return this;
  243. })(jsPDF.API);