# modules/split_text_to_size.js

    /* global jsPDF */
    /** @license
     * MIT license.
     * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
     *               2014 Diego Casorran, https://github.com/diegocr
     *
     * Permission is hereby granted, free of charge, to any person obtaining
     * a copy of this software and associated documentation files (the
     * "Software"), to deal in the Software without restriction, including
     * without limitation the rights to use, copy, modify, merge, publish,
     * distribute, sublicense, and/or sell copies of the Software, and to
     * permit persons to whom the Software is furnished to do so, subject to
     * the following conditions:
     *
     * The above copyright notice and this permission notice shall be
     * included in all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     * ====================================================================
     */

    /**
    * jsPDF split_text_to_size plugin
    *
    * @name split_text_to_size
    * @module
    */
    (function (API) {
      'use strict'
      /**
       * Returns an array of length matching length of the 'word' string, with each
       * cell occupied by the width of the char in that position.
       *
       * @name getCharWidthsArray
       * @function
       * @param {string} text
       * @param {Object} options
       * @returns {Array}
       */
      var getCharWidthsArray = API.getCharWidthsArray = function (text, options) {
        options = options || {};

        var activeFont = options.font || this.internal.getFont();
        var fontSize = options.fontSize || this.internal.getFontSize();
        var charSpace = options.charSpace || this.internal.getCharSpace();

        var widths = options.widths ? options.widths : activeFont.metadata.Unicode.widths;
        var widthsFractionOf = widths.fof ? widths.fof : 1;
        var kerning = options.kerning ? options.kerning : activeFont.metadata.Unicode.kerning;
        var kerningFractionOf = kerning.fof ? kerning.fof : 1;
        var doKerning = (options.doKerning === false) ? false : true;
        var kerningValue = 0;

        var i;
        var length = text.length;
        var char_code;
        var prior_char_code = 0; //for kerning
        var default_char_width = widths[0] || widthsFractionOf;
        var output = [];

        for (i = 0; i < length; i++) {
            char_code = text.charCodeAt(i);

            if (typeof activeFont.metadata.widthOfString === "function") {
                output.push(((activeFont.metadata.widthOfGlyph(activeFont.metadata.characterToGlyph(char_code)) + charSpace * (1000/ fontSize)) || 0) / 1000);
            } else {
                if ( doKerning && typeof(kerning[char_code]) === 'object' && !isNaN(parseInt(kerning[char_code][prior_char_code], 10))) {
                    kerningValue = kerning[char_code][prior_char_code] / kerningFractionOf;
                }
              output.push(((widths[char_code] || default_char_width) / widthsFractionOf) + kerningValue);
            }
            prior_char_code = char_code;
        }

        return output;
      }

      /**
      * Returns a widths of string in a given font, if the font size is set as 1 point.
      *
      * In other words, this is "proportional" value. For 1 unit of font size, the length
      * of the string will be that much.
      *
      * Multiply by font size to get actual width in *points*
      * Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc.
      *
      * @name getStringUnitWidth
      * @public
      * @function
      * @param {string} text
      * @param {string} options
      * @returns {number} result
      */
      var getStringUnitWidth = API.getStringUnitWidth = function (text, options) {
        options = options || {};

        var fontSize = options.fontSize || this.internal.getFontSize();
        var font = options.font || this.internal.getFont();
        var charSpace = options.charSpace || this.internal.getCharSpace();
        var result = 0;

        if (API.processArabic) {
          text = API.processArabic(text);
        }

        if (typeof font.metadata.widthOfString === "function") {
          result = font.metadata.widthOfString(text, fontSize, charSpace) / fontSize;
        } else {
          result = getCharWidthsArray.apply(this, arguments).reduce(function(pv, cv) { return pv + cv; }, 0);
        }
        return result;
      };

      /**
      returns array of lines
      */
      var splitLongWord = function (word, widths_array, firstLineMaxLen, maxLen) {
        var answer = []

        // 1st, chop off the piece that can fit on the hanging line.
        var i = 0,
          l = word.length,
          workingLen = 0
        while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) {
          workingLen += widths_array[i];
          i++;
        }
        // this is first line.
        answer.push(word.slice(0, i))

        // 2nd. Split the rest into maxLen pieces.
        var startOfLine = i
        workingLen = 0
        while (i !== l) {
          if (workingLen + widths_array[i] > maxLen) {
            answer.push(word.slice(startOfLine, i))
            workingLen = 0
            startOfLine = i
          }
          workingLen += widths_array[i];
          i++;
        }
        if (startOfLine !== i) {
          answer.push(word.slice(startOfLine, i))
        }

        return answer
      }

      // Note, all sizing inputs for this function must be in "font measurement units"
      // By default, for PDF, it's "point".
      var splitParagraphIntoLines = function (text, maxlen, options) {
        // at this time works only on Western scripts, ones with space char
        // separating the words. Feel free to expand.

        if (!options) {
          options = {}
        }

        var line = [],
          lines = [line],
          line_length = options.textIndent || 0,
          separator_length = 0,
          current_word_length = 0,
          word, widths_array, words = text.split(' '),
          spaceCharWidth = getCharWidthsArray.apply(this, [' ', options])[0],
          i, l, tmp, lineIndent

        if (options.lineIndent === -1) {
          lineIndent = words[0].length + 2;
        } else {
          lineIndent = options.lineIndent || 0;
        }
        if (lineIndent) {
          var pad = Array(lineIndent).join(" "),
            wrds = [];
          words.map(function (wrd) {
            wrd = wrd.split(/\s*\n/);
            if (wrd.length > 1) {
              wrds = wrds.concat(wrd.map(function (wrd, idx) {
                return (idx && wrd.length ? "\n" : "") + wrd;
              }));
            } else {
              wrds.push(wrd[0]);
            }
          });
          words = wrds;
          lineIndent = getStringUnitWidth.apply(this, [pad, options]);
        }

        for (i = 0, l = words.length; i < l; i++) {
          var force = 0;

          word = words[i]
          if (lineIndent && word[0] == "\n") {
            word = word.substr(1);
            force = 1;
          }
          widths_array = getCharWidthsArray.apply(this, [word, options])
          current_word_length = widths_array.reduce(function(pv, cv) { return pv + cv; }, 0);

          if (line_length + separator_length + current_word_length > maxlen || force) {
            if (current_word_length > maxlen) {
              // this happens when you have space-less long URLs for example.
              // we just chop these to size. We do NOT insert hiphens
              tmp = splitLongWord.apply(this, [word, widths_array, maxlen - (line_length + separator_length), maxlen]);
              // first line we add to existing line object
              line.push(tmp.shift()) // it's ok to have extra space indicator there
              // last line we make into new line object
              line = [tmp.pop()]
              // lines in the middle we apped to lines object as whole lines
              while (tmp.length) {
                lines.push([tmp.shift()]) // single fragment occupies whole line
              }
              current_word_length = widths_array.slice(word.length - (line[0] ? line[0].length : 0)).reduce(function(pv, cv) { return pv + cv; }, 0);
            } else {
              // just put it on a new line
              line = [word]
            }

            // now we attach new line to lines
            lines.push(line)
            line_length = current_word_length + lineIndent
            separator_length = spaceCharWidth

          } else {
            line.push(word)

            line_length += separator_length + current_word_length
            separator_length = spaceCharWidth
          }
        }

        var postProcess;
        if (lineIndent) {
          postProcess = function (ln, idx) {
            return (idx ? pad : '') + ln.join(" ");
          };
        } else {
          postProcess = function (ln) {
            return ln.join(" ")
          };
        }

        return lines.map(postProcess);
      }

      /**
      * Splits a given string into an array of strings. Uses 'size' value
      * (in measurement units declared as default for the jsPDF instance)
      * and the font's "widths" and "Kerning" tables, where available, to
      * determine display length of a given string for a given font.
      *
      * We use character's 100% of unit size (height) as width when Width
      * table or other default width is not available.
      *
      * @name splitTextToSize
      * @public
      * @function
      * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string.
      * @param {number} size Nominal number, measured in units default to this instance of jsPDF.
      * @param {Object} options Optional flags needed for chopper to do the right thing.
      * @returns {Array} array Array with strings chopped to size.
      */
      API.splitTextToSize = function (text, maxlen, options) {
        'use strict'

        options = options || {};

        var fsize = options.fontSize || this.internal.getFontSize(),
          newOptions = (function (options) {
            var widths = {
                0: 1
              },
              kerning = {}

            if (!options.widths || !options.kerning) {
              var f = this.internal.getFont(options.fontName, options.fontStyle),
                encoding = 'Unicode'
              // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE
              // Actual JavaScript-native String's 16bit char codes used.
              // no multi-byte logic here

              if (f.metadata[encoding]) {
                return {
                  widths: f.metadata[encoding].widths || widths,
                  kerning: f.metadata[encoding].kerning || kerning
                }
              } else {
                return {
                  font: f.metadata,
                  fontSize: this.internal.getFontSize(),
                  charSpace: this.internal.getCharSpace()
                }
              }
            } else {
              return {
                widths: options.widths,
                kerning: options.kerning
              }
            }
          }).call(this, options)

        // first we split on end-of-line chars
        var paragraphs
        if (Array.isArray(text)) {
          paragraphs = text;
        } else {
          paragraphs = text.split(/\r?\n/);
        }

        // now we convert size (max length of line) into "font size units"
        // at present time, the "font size unit" is always 'point'
        // 'proportional' means, "in proportion to font size"
        var fontUnit_maxLen = 1.0 * this.internal.scaleFactor * maxlen / fsize
        // at this time, fsize is always in "points" regardless of the default measurement unit of the doc.
        // this may change in the future?
        // until then, proportional_maxlen is likely to be in 'points'

        // If first line is to be indented (shorter or longer) than maxLen
        // we indicate that by using CSS-style "text-indent" option.
        // here it's in font units too (which is likely 'points')
        // it can be negative (which makes the first line longer than maxLen)
        newOptions.textIndent = options.textIndent ?
          options.textIndent * 1.0 * this.internal.scaleFactor / fsize :
          0
        newOptions.lineIndent = options.lineIndent;

        var i, l, output = []
        for (i = 0, l = paragraphs.length; i < l; i++) {
          output = output.concat(
            splitParagraphIntoLines.apply(this, [paragraphs[i], fontUnit_maxLen, newOptions])
          )
        }

        return output
      }

    })(jsPDF.API);