# modules/split_text_to_size.js

    /** @license
     * MIT license.
     * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems
     *               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.
     * ====================================================================
     */

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

    /**
     * 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;
            } else {
              kerningValue = 0;
            }
            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 = String(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);