modules/annotations.js

  1. /* global jsPDF */
  2. /**
  3. * @license
  4. * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
  5. *
  6. * Licensed under the MIT License.
  7. * http://opensource.org/licenses/mit-license
  8. */
  9. /**
  10. * jsPDF Annotations PlugIn
  11. *
  12. * There are many types of annotations in a PDF document. Annotations are placed
  13. * on a page at a particular location. They are not 'attached' to an object.
  14. * <br />
  15. * This plugin current supports <br />
  16. * <li> Goto Page (set pageNumber and top in options)
  17. * <li> Goto Name (set name and top in options)
  18. * <li> Goto URL (set url in options)
  19. * <p>
  20. * The destination magnification factor can also be specified when goto is a page number or a named destination. (see documentation below)
  21. * (set magFactor in options). XYZ is the default.
  22. * </p>
  23. * <p>
  24. * Links, Text, Popup, and FreeText are supported.
  25. * </p>
  26. * <p>
  27. * Options In PDF spec Not Implemented Yet
  28. * <li> link border
  29. * <li> named target
  30. * <li> page coordinates
  31. * <li> destination page scaling and layout
  32. * <li> actions other than URL and GotoPage
  33. * <li> background / hover actions
  34. * </p>
  35. * @name annotations
  36. * @module
  37. */
  38. /*
  39. Destination Magnification Factors
  40. See PDF 1.3 Page 386 for meanings and options
  41. [supported]
  42. XYZ (options; left top zoom)
  43. Fit (no options)
  44. FitH (options: top)
  45. FitV (options: left)
  46. [not supported]
  47. FitR
  48. FitB
  49. FitBH
  50. FitBV
  51. */
  52. (function (jsPDFAPI) {
  53. 'use strict';
  54. var notEmpty = function (obj) {
  55. if (typeof obj != 'undefined') {
  56. if (obj != '') {
  57. return true;
  58. }
  59. }
  60. };
  61. jsPDF.API.events.push(['addPage', function (addPageData) {
  62. var pageInfo = this.internal.getPageInfo(addPageData.pageNumber);
  63. pageInfo.pageContext.annotations = [];
  64. }]);
  65. jsPDFAPI.events.push(['putPage', function (putPageData) {
  66. var getHorizontalCoordinateString = this.internal.getCoordinateString;
  67. var getVerticalCoordinateString = this.internal.getVerticalCoordinateString;
  68. var pageInfo = this.internal.getPageInfoByObjId(putPageData.objId);
  69. var pageAnnos = putPageData.pageContext.annotations;
  70. var found = false;
  71. for (var a = 0; a < pageAnnos.length && !found; a++) {
  72. var anno = pageAnnos[a];
  73. switch (anno.type) {
  74. case 'link':
  75. if (notEmpty(anno.options.url) || notEmpty(anno.options.pageNumber)) {
  76. found = true;
  77. }
  78. break;
  79. case 'reference':
  80. case 'text':
  81. case 'freetext':
  82. found = true;
  83. break;
  84. }
  85. }
  86. if (found == false) {
  87. return;
  88. }
  89. this.internal.write("/Annots [");
  90. for (var i = 0; i < pageAnnos.length; i++) {
  91. var anno = pageAnnos[i];
  92. switch (anno.type) {
  93. case 'reference':
  94. // References to Widget Annotations (for AcroForm Fields)
  95. this.internal.write(' ' + anno.object.objId + ' 0 R ');
  96. break;
  97. case 'text':
  98. // Create a an object for both the text and the popup
  99. var objText = this.internal.newAdditionalObject();
  100. var objPopup = this.internal.newAdditionalObject();
  101. var title = anno.title || 'Note';
  102. var rect = "/Rect [" + getHorizontalCoordinateString(anno.bounds.x) + " " + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + " " + getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) + " " + getVerticalCoordinateString(anno.bounds.y) + "] ";
  103. line = '<</Type /Annot /Subtype /' + 'Text' + ' ' + rect + '/Contents (' + anno.contents + ')';
  104. line += ' /Popup ' + objPopup.objId + " 0 R";
  105. line += ' /P ' + pageInfo.objId + " 0 R";
  106. line += ' /T (' + title + ') >>';
  107. objText.content = line;
  108. var parent = objText.objId + ' 0 R';
  109. var popoff = 30;
  110. var rect = "/Rect [" + getHorizontalCoordinateString(anno.bounds.x + popoff) + " " + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + " " + getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w + popoff) + " " + getVerticalCoordinateString(anno.bounds.y) + "] ";
  111. line = '<</Type /Annot /Subtype /' + 'Popup' + ' ' + rect + ' /Parent ' + parent;
  112. if (anno.open) {
  113. line += ' /Open true';
  114. }
  115. line += ' >>';
  116. objPopup.content = line;
  117. this.internal.write(objText.objId, '0 R', objPopup.objId, '0 R');
  118. break;
  119. case 'freetext':
  120. var rect = "/Rect [" + getHorizontalCoordinateString(anno.bounds.x) + " " + getVerticalCoordinateString(anno.bounds.y) + " " + getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) + " " + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + "] ";
  121. var color = anno.color || '#000000';
  122. line = '<</Type /Annot /Subtype /' + 'FreeText' + ' ' + rect + '/Contents (' + anno.contents + ')';
  123. line += ' /DS(font: Helvetica,sans-serif 12.0pt; text-align:left; color:#' + color + ')';
  124. line += ' /Border [0 0 0]';
  125. line += ' >>';
  126. this.internal.write(line);
  127. break;
  128. case 'link':
  129. if (anno.options.name) {
  130. var loc = this.annotations._nameMap[anno.options.name];
  131. anno.options.pageNumber = loc.page;
  132. anno.options.top = loc.y;
  133. } else {
  134. if (!anno.options.top) {
  135. anno.options.top = 0;
  136. }
  137. }
  138. var rect = "/Rect [" + getHorizontalCoordinateString(anno.x) + " " + getVerticalCoordinateString(anno.y) + " " + getHorizontalCoordinateString(anno.x + anno.w) + " " + getVerticalCoordinateString(anno.y + anno.h) + "] ";
  139. var line = '';
  140. if (anno.options.url) {
  141. line = '<</Type /Annot /Subtype /Link ' + rect + '/Border [0 0 0] /A <</S /URI /URI (' + anno.options.url + ') >>';
  142. } else if (anno.options.pageNumber) {
  143. // first page is 0
  144. var info = this.internal.getPageInfo(anno.options.pageNumber);
  145. line = '<</Type /Annot /Subtype /Link ' + rect + '/Border [0 0 0] /Dest [' + info.objId + " 0 R";
  146. anno.options.magFactor = anno.options.magFactor || "XYZ";
  147. switch (anno.options.magFactor) {
  148. case 'Fit':
  149. line += ' /Fit]';
  150. break;
  151. case 'FitH':
  152. line += ' /FitH ' + anno.options.top + ']';
  153. break;
  154. case 'FitV':
  155. anno.options.left = anno.options.left || 0;
  156. line += ' /FitV ' + anno.options.left + ']';
  157. break;
  158. case 'XYZ':
  159. default:
  160. var top = getVerticalCoordinateString(anno.options.top);
  161. anno.options.left = anno.options.left || 0;
  162. // 0 or null zoom will not change zoom factor
  163. if (typeof anno.options.zoom === 'undefined') {
  164. anno.options.zoom = 0;
  165. }
  166. line += ' /XYZ ' + anno.options.left + ' ' + top + ' ' + anno.options.zoom + ']';
  167. break;
  168. }
  169. }
  170. if (line != '') {
  171. line += " >>";
  172. this.internal.write(line);
  173. }
  174. break;
  175. }
  176. }
  177. this.internal.write("]");
  178. }]);
  179. /**
  180. * @name createAnnotation
  181. * @function
  182. * @param {Object} options
  183. */
  184. jsPDFAPI.createAnnotation = function (options) {
  185. var pageInfo = this.internal.getCurrentPageInfo();
  186. switch (options.type) {
  187. case 'link':
  188. this.link(options.bounds.x, options.bounds.y, options.bounds.w, options.bounds.h, options);
  189. break;
  190. case 'text':
  191. case 'freetext':
  192. pageInfo.pageContext.annotations.push(options);
  193. break;
  194. }
  195. }
  196. /**
  197. * Create a link
  198. *
  199. * valid options
  200. * <li> pageNumber or url [required]
  201. * <p>If pageNumber is specified, top and zoom may also be specified</p>
  202. * @name link
  203. * @function
  204. * @param {number} x
  205. * @param {number} y
  206. * @param {number} w
  207. * @param {number} h
  208. * @param {Object} options
  209. */
  210. jsPDFAPI.link = function (x, y, w, h, options) {
  211. var pageInfo = this.internal.getCurrentPageInfo();
  212. pageInfo.pageContext.annotations.push({
  213. x: x,
  214. y: y,
  215. w: w,
  216. h: h,
  217. options: options,
  218. type: 'link'
  219. });
  220. };
  221. /**
  222. * Currently only supports single line text.
  223. * Returns the width of the text/link
  224. *
  225. * @name textWithLink
  226. * @function
  227. * @param {string} text
  228. * @param {number} x
  229. * @param {number} y
  230. * @param {Object} options
  231. * @returns {number} width the width of the text/link
  232. */
  233. jsPDFAPI.textWithLink = function (text, x, y, options) {
  234. var width = this.getTextWidth(text);
  235. var height = this.internal.getLineHeight() / this.internal.scaleFactor;
  236. this.text(text, x, y, options);
  237. //TODO We really need the text baseline height to do this correctly.
  238. // Or ability to draw text on top, bottom, center, or baseline.
  239. y += height * .2;
  240. this.link(x, y - height, width, height, options);
  241. return width;
  242. };
  243. //TODO move into external library
  244. /**
  245. * @name getTextWidth
  246. * @function
  247. * @param {string} text
  248. * @returns {number} txtWidth
  249. */
  250. jsPDFAPI.getTextWidth = function (text) {
  251. var fontSize = this.internal.getFontSize();
  252. var txtWidth = this.getStringUnitWidth(text) * fontSize / this.internal.scaleFactor;
  253. return txtWidth;
  254. };
  255. return this;
  256. })(jsPDF.API);