# modules/annotations.js

    /**
     * @license
     * Copyright (c) 2014 Steven Spungin (TwelveTone LLC)  steven@twelvetone.tv
     *
     * Licensed under the MIT License.
     * http://opensource.org/licenses/mit-license
     */

    /**
     * jsPDF Annotations PlugIn
     *
     * There are many types of annotations in a PDF document. Annotations are placed
     * on a page at a particular location. They are not 'attached' to an object.
     * <br />
     * This plugin current supports <br />
     * <li> Goto Page (set pageNumber and top in options)
     * <li> Goto Name (set name and top in options)
     * <li> Goto URL (set url in options)
     * <p>
     * 	The destination magnification factor can also be specified when goto is a page number or a named destination. (see documentation below)
     *  (set magFactor in options).  XYZ is the default.
     * </p>
     * <p>
     *  Links, Text, Popup, and FreeText are supported.
     * </p>
     * <p>
     * Options In PDF spec Not Implemented Yet
     * <li> link border
     * <li> named target
     * <li> page coordinates
     * <li> destination page scaling and layout
     * <li> actions other than URL and GotoPage
     * <li> background / hover actions
     * </p>
     * @name annotations
     * @module
     */

    /*
        Destination Magnification Factors
        See PDF 1.3 Page 386 for meanings and options

        [supported]
    	XYZ (options; left top zoom)
    	Fit (no options)
    	FitH (options: top)
    	FitV (options: left)

    	[not supported]
    	FitR
    	FitB
    	FitBH
    	FitBV
     */

    import { jsPDF } from "../jspdf.js";

    (function(jsPDFAPI) {
      "use strict";

      var notEmpty = function(obj) {
        if (typeof obj != "undefined") {
          if (obj != "") {
            return true;
          }
        }
      };

      jsPDF.API.events.push([
        "addPage",
        function(addPageData) {
          var pageInfo = this.internal.getPageInfo(addPageData.pageNumber);
          pageInfo.pageContext.annotations = [];
        }
      ]);

      jsPDFAPI.events.push([
        "putPage",
        function(putPageData) {
          var getHorizontalCoordinateString = this.internal.getCoordinateString;
          var getVerticalCoordinateString = this.internal
            .getVerticalCoordinateString;
          var pageInfo = this.internal.getPageInfoByObjId(putPageData.objId);
          var pageAnnos = putPageData.pageContext.annotations;

          var anno, rect, line;
          var found = false;
          for (var a = 0; a < pageAnnos.length && !found; a++) {
            anno = pageAnnos[a];
            switch (anno.type) {
              case "link":
                if (
                  notEmpty(anno.options.url) ||
                  notEmpty(anno.options.pageNumber)
                ) {
                  found = true;
                }
                break;
              case "reference":
              case "text":
              case "freetext":
                found = true;
                break;
            }
          }
          if (found == false) {
            return;
          }

          this.internal.write("/Annots [");
          for (var i = 0; i < pageAnnos.length; i++) {
            anno = pageAnnos[i];
            var escape = this.internal.pdfEscape;
            var encryptor = this.internal.getEncryptor(putPageData.objId);

            switch (anno.type) {
              case "reference":
                // References to Widget Annotations (for AcroForm Fields)
                this.internal.write(" " + anno.object.objId + " 0 R ");
                break;
              case "text":
                // Create a an object for both the text and the popup
                var objText = this.internal.newAdditionalObject();
                var objPopup = this.internal.newAdditionalObject();
                var encryptorText = this.internal.getEncryptor(objText.objId);

                var title = anno.title || "Note";
                rect =
                  "/Rect [" +
                  getHorizontalCoordinateString(anno.bounds.x) +
                  " " +
                  getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) +
                  " " +
                  getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) +
                  " " +
                  getVerticalCoordinateString(anno.bounds.y) +
                  "] ";

                line =
                  "<</Type /Annot /Subtype /" +
                  "Text" +
                  " " +
                  rect +
                  "/Contents (" +
                  escape(encryptorText(anno.contents)) +
                  ")";
                line += " /Popup " + objPopup.objId + " 0 R";
                line += " /P " + pageInfo.objId + " 0 R";
                line += " /T (" + escape(encryptorText(title)) + ") >>";
                objText.content = line;

                var parent = objText.objId + " 0 R";
                var popoff = 30;
                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) +
                  "] ";
                line =
                  "<</Type /Annot /Subtype /" +
                  "Popup" +
                  " " +
                  rect +
                  " /Parent " +
                  parent;
                if (anno.open) {
                  line += " /Open true";
                }
                line += " >>";
                objPopup.content = line;

                this.internal.write(objText.objId, "0 R", objPopup.objId, "0 R");

                break;
              case "freetext":
                rect =
                  "/Rect [" +
                  getHorizontalCoordinateString(anno.bounds.x) +
                  " " +
                  getVerticalCoordinateString(anno.bounds.y) +
                  " " +
                  getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) +
                  " " +
                  getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) +
                  "] ";
                var color = anno.color || "#000000";
                line =
                  "<</Type /Annot /Subtype /" +
                  "FreeText" +
                  " " +
                  rect +
                  "/Contents (" +
                  escape(encryptor(anno.contents)) +
                  ")";
                line +=
                  " /DS(font: Helvetica,sans-serif 12.0pt; text-align:left; color:#" +
                  color +
                  ")";
                line += " /Border [0 0 0]";
                line += " >>";
                this.internal.write(line);
                break;
              case "link":
                if (anno.options.name) {
                  var loc = this.annotations._nameMap[anno.options.name];
                  anno.options.pageNumber = loc.page;
                  anno.options.top = loc.y;
                } else {
                  if (!anno.options.top) {
                    anno.options.top = 0;
                  }
                }

                rect =
                  "/Rect [" +
                  anno.finalBounds.x +
                  " " +
                  anno.finalBounds.y +
                  " " +
                  anno.finalBounds.w +
                  " " +
                  anno.finalBounds.h +
                  "] ";

                line = "";
                if (anno.options.url) {
                  line =
                    "<</Type /Annot /Subtype /Link " +
                    rect +
                    "/Border [0 0 0] /A <</S /URI /URI (" +
                    escape(encryptor(anno.options.url)) +
                    ") >>";
                } else if (anno.options.pageNumber) {
                  // first page is 0
                  var info = this.internal.getPageInfo(anno.options.pageNumber);
                  line =
                    "<</Type /Annot /Subtype /Link " +
                    rect +
                    "/Border [0 0 0] /Dest [" +
                    info.objId +
                    " 0 R";
                  anno.options.magFactor = anno.options.magFactor || "XYZ";
                  switch (anno.options.magFactor) {
                    case "Fit":
                      line += " /Fit]";
                      break;
                    case "FitH":
                      line += " /FitH " + anno.options.top + "]";
                      break;
                    case "FitV":
                      anno.options.left = anno.options.left || 0;
                      line += " /FitV " + anno.options.left + "]";
                      break;
                    case "XYZ":
                    default:
                      var top = getVerticalCoordinateString(anno.options.top);
                      anno.options.left = anno.options.left || 0;
                      // 0 or null zoom will not change zoom factor
                      if (typeof anno.options.zoom === "undefined") {
                        anno.options.zoom = 0;
                      }
                      line +=
                        " /XYZ " +
                        anno.options.left +
                        " " +
                        top +
                        " " +
                        anno.options.zoom +
                        "]";
                      break;
                  }
                }

                if (line != "") {
                  line += " >>";
                  this.internal.write(line);
                }
                break;
            }
          }
          this.internal.write("]");
        }
      ]);

      /**
       * @name createAnnotation
       * @function
       * @param {Object} options
       */
      jsPDFAPI.createAnnotation = function(options) {
        var pageInfo = this.internal.getCurrentPageInfo();
        switch (options.type) {
          case "link":
            this.link(
              options.bounds.x,
              options.bounds.y,
              options.bounds.w,
              options.bounds.h,
              options
            );
            break;
          case "text":
          case "freetext":
            pageInfo.pageContext.annotations.push(options);
            break;
        }
      };

      /**
       * Create a link
       *
       * valid options
       * <li> pageNumber or url [required]
       * <p>If pageNumber is specified, top and zoom may also be specified</p>
       * @name link
       * @function
       * @param {number} x
       * @param {number} y
       * @param {number} w
       * @param {number} h
       * @param {Object} options
       */
      jsPDFAPI.link = function(x, y, w, h, options) {
        var pageInfo = this.internal.getCurrentPageInfo();
        var getHorizontalCoordinateString = this.internal.getCoordinateString;
        var getVerticalCoordinateString = this.internal.getVerticalCoordinateString;

        pageInfo.pageContext.annotations.push({
          finalBounds: {
            x: getHorizontalCoordinateString(x),
            y: getVerticalCoordinateString(y),
            w: getHorizontalCoordinateString(x + w),
            h: getVerticalCoordinateString(y + h)
          },
          options: options,
          type: "link"
        });
      };

      /**
       * Currently only supports single line text.
       * Returns the width of the text/link
       *
       * @name textWithLink
       * @function
       * @param {string} text
       * @param {number} x
       * @param {number} y
       * @param {Object} options
       * @returns {number} width the width of the text/link
       */
      jsPDFAPI.textWithLink = function(text, x, y, options) {
        var totalLineWidth = this.getTextWidth(text);
        var lineHeight = this.internal.getLineHeight() / this.internal.scaleFactor;
        var linkHeight, linkWidth;

        // Checking if maxWidth option is passed to determine lineWidth and number of lines for each line
        if (options.maxWidth !== undefined) {
          var { maxWidth } = options;
          linkWidth = maxWidth;
          var numOfLines = this.splitTextToSize(text, linkWidth).length;
          linkHeight = Math.ceil(lineHeight * numOfLines);
        } else {
          linkWidth = totalLineWidth;
          linkHeight = lineHeight;
        }

        this.text(text, x, y, options);

        //TODO We really need the text baseline height to do this correctly.
        // Or ability to draw text on top, bottom, center, or baseline.
        y += lineHeight * 0.2;
        //handle x position based on the align option
        if (options.align === "center") {
          x = x - totalLineWidth / 2; //since starting from center move the x position by half of text width
        }
        if (options.align === "right") {
          x = x - totalLineWidth;
        }
        this.link(x, y - lineHeight, linkWidth, linkHeight, options);
        return totalLineWidth;
      };

      //TODO move into external library
      /**
       * @name getTextWidth
       * @function
       * @param {string} text
       * @returns {number} txtWidth
       */
      jsPDFAPI.getTextWidth = function(text) {
        var fontSize = this.internal.getFontSize();
        var txtWidth =
          (this.getStringUnitWidth(text) * fontSize) / this.internal.scaleFactor;
        return txtWidth;
      };

      return this;
    })(jsPDF.API);