modules/utf8.js

import { jsPDF } from "../jspdf.js";
import { toPDFName } from "../libs/pdfname.js";

/**
 * @name utf8
 * @module
 */
(function(jsPDF) {
  "use strict";
  var jsPDFAPI = jsPDF.API;

  /***************************************************************************************************/
  /* function : pdfEscape16                                                                          */
  /* comment : The character id of a 2-byte string is converted to a hexadecimal number by obtaining */
  /*   the corresponding glyph id and width, and then adding padding to the string.                  */
  /***************************************************************************************************/
  var pdfEscape16 = (jsPDFAPI.pdfEscape16 = function(text, font) {
    var widths = font.metadata.Unicode.widths;
    var padz = ["", "0", "00", "000", "0000"];
    var ar = [""];
    for (var i = 0, l = text.length, t; i < l; ++i) {
      t = font.metadata.characterToGlyph(text.charCodeAt(i));
      font.metadata.glyIdsUsed.push(t);
      font.metadata.toUnicode[t] = text.charCodeAt(i);
      if (widths.indexOf(t) == -1) {
        widths.push(t);
        widths.push([parseInt(font.metadata.widthOfGlyph(t), 10)]);
      }
      if (t == "0") {
        //Spaces are not allowed in cmap.
        return ar.join("");
      } else {
        t = t.toString(16);
        ar.push(padz[4 - t.length], t);
      }
    }
    return ar.join("");
  });

  var toUnicodeCmap = function(map) {
    var code, codes, range, unicode, unicodeMap, _i, _len;
    unicodeMap =
      "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n  /Registry (Adobe)\n  /Ordering (UCS)\n  /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000><ffff>\nendcodespacerange";
    codes = Object.keys(map).sort(function(a, b) {
      return a - b;
    });

    range = [];
    for (_i = 0, _len = codes.length; _i < _len; _i++) {
      code = codes[_i];
      if (range.length >= 100) {
        unicodeMap +=
          "\n" +
          range.length +
          " beginbfchar\n" +
          range.join("\n") +
          "\nendbfchar";
        range = [];
      }

      if (
        map[code] !== undefined &&
        map[code] !== null &&
        typeof map[code].toString === "function"
      ) {
        unicode = ("0000" + map[code].toString(16)).slice(-4);
        code = ("0000" + (+code).toString(16)).slice(-4);
        range.push("<" + code + "><" + unicode + ">");
      }
    }

    if (range.length) {
      unicodeMap +=
        "\n" +
        range.length +
        " beginbfchar\n" +
        range.join("\n") +
        "\nendbfchar\n";
    }
    unicodeMap +=
      "endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend";
    return unicodeMap;
  };

  var identityHFunction = function(options) {
    var font = options.font;
    var out = options.out;
    var newObject = options.newObject;
    var putStream = options.putStream;

    if (
      font.metadata instanceof jsPDF.API.TTFFont &&
      font.encoding === "Identity-H"
    ) {
      //Tag with Identity-H
      var widths = font.metadata.Unicode.widths;
      var data = font.metadata.subset.encode(font.metadata.glyIdsUsed, 1);
      var pdfOutput = data;
      var pdfOutput2 = "";
      for (var i = 0; i < pdfOutput.length; i++) {
        pdfOutput2 += String.fromCharCode(pdfOutput[i]);
      }
      var fontTable = newObject();
      putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable });
      out("endobj");

      var cmap = newObject();
      var cmapData = toUnicodeCmap(font.metadata.toUnicode);
      putStream({ data: cmapData, addLength1: true, objectId: cmap });
      out("endobj");

      var fontDescriptor = newObject();
      out("<<");
      out("/Type /FontDescriptor");
      out("/FontName /" + toPDFName(font.fontName));
      out("/FontFile2 " + fontTable + " 0 R");
      out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox));
      out("/Flags " + font.metadata.flags);
      out("/StemV " + font.metadata.stemV);
      out("/ItalicAngle " + font.metadata.italicAngle);
      out("/Ascent " + font.metadata.ascender);
      out("/Descent " + font.metadata.decender);
      out("/CapHeight " + font.metadata.capHeight);
      out(">>");
      out("endobj");

      var DescendantFont = newObject();
      out("<<");
      out("/Type /Font");
      out("/BaseFont /" + toPDFName(font.fontName));
      out("/FontDescriptor " + fontDescriptor + " 0 R");
      out("/W " + jsPDF.API.PDFObject.convert(widths));
      out("/CIDToGIDMap /Identity");
      out("/DW 1000");
      out("/Subtype /CIDFontType2");
      out("/CIDSystemInfo");
      out("<<");
      out("/Supplement 0");
      out("/Registry (Adobe)");
      out("/Ordering (" + font.encoding + ")");
      out(">>");
      out(">>");
      out("endobj");

      font.objectNumber = newObject();
      out("<<");
      out("/Type /Font");
      out("/Subtype /Type0");
      out("/ToUnicode " + cmap + " 0 R");
      out("/BaseFont /" + toPDFName(font.fontName));
      out("/Encoding /" + font.encoding);
      out("/DescendantFonts [" + DescendantFont + " 0 R]");
      out(">>");
      out("endobj");

      font.isAlreadyPutted = true;
    }
  };

  jsPDFAPI.events.push([
    "putFont",
    function(args) {
      identityHFunction(args);
    }
  ]);

  var winAnsiEncodingFunction = function(options) {
    var font = options.font;
    var out = options.out;
    var newObject = options.newObject;
    var putStream = options.putStream;

    if (
      font.metadata instanceof jsPDF.API.TTFFont &&
      font.encoding === "WinAnsiEncoding"
    ) {
      //Tag with WinAnsi encoding
      var data = font.metadata.rawData;
      var pdfOutput = data;
      var pdfOutput2 = "";
      for (var i = 0; i < pdfOutput.length; i++) {
        pdfOutput2 += String.fromCharCode(pdfOutput[i]);
      }
      var fontTable = newObject();
      putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable });
      out("endobj");

      var cmap = newObject();
      var cmapData = toUnicodeCmap(font.metadata.toUnicode);
      putStream({ data: cmapData, addLength1: true, objectId: cmap });
      out("endobj");

      var fontDescriptor = newObject();
      out("<<");
      out("/Descent " + font.metadata.decender);
      out("/CapHeight " + font.metadata.capHeight);
      out("/StemV " + font.metadata.stemV);
      out("/Type /FontDescriptor");
      out("/FontFile2 " + fontTable + " 0 R");
      out("/Flags 96");
      out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox));
      out("/FontName /" + toPDFName(font.fontName));
      out("/ItalicAngle " + font.metadata.italicAngle);
      out("/Ascent " + font.metadata.ascender);
      out(">>");
      out("endobj");
      font.objectNumber = newObject();
      for (var j = 0; j < font.metadata.hmtx.widths.length; j++) {
        font.metadata.hmtx.widths[j] = parseInt(
          font.metadata.hmtx.widths[j] * (1000 / font.metadata.head.unitsPerEm)
        ); //Change the width of Em units to Point units.
      }
      out(
        "<</Subtype/TrueType/Type/Font/ToUnicode " +
          cmap +
          " 0 R/BaseFont/" +
          toPDFName(font.fontName) +
          "/FontDescriptor " +
          fontDescriptor +
          " 0 R" +
          "/Encoding/" +
          font.encoding +
          " /FirstChar 29 /LastChar 255 /Widths " +
          jsPDF.API.PDFObject.convert(font.metadata.hmtx.widths) +
          ">>"
      );
      out("endobj");
      font.isAlreadyPutted = true;
    }
  };

  jsPDFAPI.events.push([
    "putFont",
    function(args) {
      winAnsiEncodingFunction(args);
    }
  ]);

  var utf8TextFunction = function(args) {
    var text = args.text || "";
    var x = args.x;
    var y = args.y;
    var options = args.options || {};
    var mutex = args.mutex || {};

    var pdfEscape = mutex.pdfEscape;
    var activeFontKey = mutex.activeFontKey;
    var fonts = mutex.fonts;
    var key = activeFontKey;

    var str = "",
      s = 0,
      cmapConfirm;
    var strText = "";
    var encoding = fonts[key].encoding;

    if (fonts[key].encoding !== "Identity-H") {
      return {
        text: text,
        x: x,
        y: y,
        options: options,
        mutex: mutex
      };
    }
    strText = text;

    key = activeFontKey;
    if (Array.isArray(text)) {
      strText = text[0];
    }
    for (s = 0; s < strText.length; s += 1) {
      if (fonts[key].metadata.hasOwnProperty("cmap")) {
        cmapConfirm =
          fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)];
        /*
             if (Object.prototype.toString.call(text) === '[object Array]') {
                var i = 0;
               // for (i = 0; i < text.length; i += 1) {
                    if (Object.prototype.toString.call(text[s]) === '[object Array]') {
                        cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s][0].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id
                    } else {

                    }
                //}

            } else {
                cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id
            }*/
      }
      if (!cmapConfirm) {
        if (
          strText[s].charCodeAt(0) < 256 &&
          fonts[key].metadata.hasOwnProperty("Unicode")
        ) {
          str += strText[s];
        } else {
          str += "";
        }
      } else {
        str += strText[s];
      }
    }
    var result = "";
    if (parseInt(key.slice(1)) < 14 || encoding === "WinAnsiEncoding") {
      //For the default 13 font
      result = pdfEscape(str, key)
        .split("")
        .map(function(cv) {
          return cv.charCodeAt(0).toString(16);
        })
        .join("");
    } else if (encoding === "Identity-H") {
      result = pdfEscape16(str, fonts[key]);
    }
    mutex.isHex = true;

    return {
      text: result,
      x: x,
      y: y,
      options: options,
      mutex: mutex
    };
  };

  var utf8EscapeFunction = function(parms) {
    var text = parms.text || "",
      x = parms.x,
      y = parms.y,
      options = parms.options,
      mutex = parms.mutex;
    var tmpText = [];
    var args = {
      text: text,
      x: x,
      y: y,
      options: options,
      mutex: mutex
    };

    if (Array.isArray(text)) {
      var i = 0;
      for (i = 0; i < text.length; i += 1) {
        if (Array.isArray(text[i])) {
          if (text[i].length === 3) {
            tmpText.push([
              utf8TextFunction(Object.assign({}, args, { text: text[i][0] }))
                .text,
              text[i][1],
              text[i][2]
            ]);
          } else {
            tmpText.push(
              utf8TextFunction(Object.assign({}, args, { text: text[i] })).text
            );
          }
        } else {
          tmpText.push(
            utf8TextFunction(Object.assign({}, args, { text: text[i] })).text
          );
        }
      }
      parms.text = tmpText;
    } else {
      parms.text = utf8TextFunction(
        Object.assign({}, args, { text: text })
      ).text;
    }
  };

  jsPDFAPI.events.push(["postProcessText", utf8EscapeFunction]);
})(jsPDF);