"use strict";

exports.__esModule = true;
exports.default = void 0;
var _TextDecorator = _interopRequireDefault(require("./TextDecorator"));
var _TextInlines = _interopRequireDefault(require("./TextInlines"));
var _variableType = require("./helpers/variableType");
var _svgToPdfkit = _interopRequireDefault(require("./3rd-party/svg-to-pdfkit"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const findFont = (fonts, requiredFonts, defaultFont) => {
  for (let i = 0; i < requiredFonts.length; i++) {
    let requiredFont = requiredFonts[i].toLowerCase();
    for (let font in fonts) {
      if (font.toLowerCase() === requiredFont) {
        return font;
      }
    }
  }
  return defaultFont;
};

/**
 * Shift the "y" height of the text baseline up or down (superscript or subscript,
 * respectively). The exact shift can / should be changed according to standard
 * conventions.
 *
 * @param {number} y
 * @param {object} inline
 * @returns {number}
 */
const offsetText = (y, inline) => {
  let newY = y;
  if (inline.sup) {
    newY -= inline.fontSize * 0.75;
  }
  if (inline.sub) {
    newY += inline.fontSize * 0.35;
  }
  return newY;
};
class Renderer {
  constructor(pdfDocument, progressCallback) {
    this.pdfDocument = pdfDocument;
    this.progressCallback = progressCallback;
    this.outlineMap = [];
  }
  renderPages(pages) {
    this.pdfDocument._pdfMakePages = pages; // TODO: Why?

    let totalItems = 0;
    if (this.progressCallback) {
      pages.forEach(page => {
        totalItems += page.items.length;
      });
    }
    let renderedItems = 0;
    for (let i = 0; i < pages.length; i++) {
      this.pdfDocument.addPage({
        size: [pages[i].pageSize.width, pages[i].pageSize.height]
      });
      let page = pages[i];
      for (let ii = 0, il = page.items.length; ii < il; ii++) {
        let item = page.items[ii];
        switch (item.type) {
          case 'vector':
            this.renderVector(item.item);
            break;
          case 'line':
            this.renderLine(item.item, item.item.x, item.item.y);
            break;
          case 'image':
            this.renderImage(item.item);
            break;
          case 'svg':
            this.renderSVG(item.item);
            break;
          case 'attachment':
            this.renderAttachment(item.item);
            break;
          case 'beginClip':
            this.beginClip(item.item);
            break;
          case 'endClip':
            this.endClip();
            break;
          case 'beginVerticalAlignment':
            this.beginVerticalAlignment(item.item);
            break;
          case 'endVerticalAlignment':
            this.endVerticalAlignment(item.item);
            break;
        }
        renderedItems++;
        if (this.progressCallback) {
          this.progressCallback(renderedItems / totalItems);
        }
      }
      if (page.watermark) {
        this.renderWatermark(page);
      }
    }
  }
  renderLine(line, x, y) {
    function preparePageNodeRefLine(_pageNodeRef, inline) {
      let newWidth;
      let diffWidth;
      let textInlines = new _TextInlines.default(null);
      if (_pageNodeRef.positions === undefined) {
        throw new Error('Page reference id not found');
      }
      let pageNumber = _pageNodeRef.positions[0].pageNumber.toString();
      inline.text = pageNumber;
      newWidth = textInlines.widthOfText(inline.text, inline);
      diffWidth = inline.width - newWidth;
      inline.width = newWidth;
      switch (inline.alignment) {
        case 'right':
          inline.x += diffWidth;
          break;
        case 'center':
          inline.x += diffWidth / 2;
          break;
      }
    }
    if (line._outline) {
      let parentOutline = this.pdfDocument.outline;
      if (line._outline.parentId && this.outlineMap[line._outline.parentId]) {
        parentOutline = this.outlineMap[line._outline.parentId];
      }
      let outline = parentOutline.addItem(line._outline.text, {
        expanded: line._outline.expanded
      });
      if (line._outline.id) {
        this.outlineMap[line._outline.id] = outline;
      }
    }
    if (line._pageNodeRef) {
      preparePageNodeRefLine(line._pageNodeRef, line.inlines[0]);
    }
    x = x || 0;
    y = y || 0;
    let lineHeight = line.getHeight();
    let ascenderHeight = line.getAscenderHeight();
    let descent = lineHeight - ascenderHeight;
    const textDecorator = new _TextDecorator.default(this.pdfDocument);
    textDecorator.drawBackground(line, x, y);

    //TODO: line.optimizeInlines();
    //TODO: lines without differently styled inlines should be written to pdf as one stream
    for (let i = 0, l = line.inlines.length; i < l; i++) {
      let inline = line.inlines[i];
      let shiftToBaseline = lineHeight - inline.font.ascender / 1000 * inline.fontSize - descent;
      if (inline._pageNodeRef) {
        preparePageNodeRefLine(inline._pageNodeRef, inline);
      }
      let options = {
        lineBreak: false,
        textWidth: inline.width,
        characterSpacing: inline.characterSpacing,
        wordCount: 1,
        link: inline.link
      };
      if (inline.linkToDestination) {
        options.goTo = inline.linkToDestination;
      }
      if (line.id && i === 0) {
        options.destination = line.id;
      }
      if (inline.fontFeatures) {
        options.features = inline.fontFeatures;
      }
      let opacity = (0, _variableType.isNumber)(inline.opacity) ? inline.opacity : 1;
      this.pdfDocument.opacity(opacity);
      this.pdfDocument.fill(this.pdfDocument.resolveColor(inline.color, 'black'));
      this.pdfDocument._font = inline.font;
      this.pdfDocument.fontSize(inline.fontSize);
      let shiftedY = offsetText(y + shiftToBaseline, inline);
      this.pdfDocument.text(inline.text, x + inline.x, shiftedY, options);
      if (inline.linkToPage) {
        this.pdfDocument.ref({
          Type: 'Action',
          S: 'GoTo',
          D: [inline.linkToPage, 0, 0]
        }).end();
        this.pdfDocument.annotate(x + inline.x, shiftedY, inline.width, inline.height, {
          Subtype: 'Link',
          Dest: [inline.linkToPage - 1, 'XYZ', null, null, null]
        });
      }
    }

    // Decorations won't draw correctly for superscript
    textDecorator.drawDecorations(line, x, y);
  }
  renderVector(vector) {
    //TODO: pdf optimization (there's no need to write all properties everytime)
    this.pdfDocument.lineWidth(vector.lineWidth || 1);
    if (vector.dash) {
      this.pdfDocument.dash(vector.dash.length, {
        space: vector.dash.space || vector.dash.length,
        phase: vector.dash.phase || 0
      });
    } else {
      this.pdfDocument.undash();
    }
    this.pdfDocument.lineJoin(vector.lineJoin || 'miter');
    this.pdfDocument.lineCap(vector.lineCap || 'butt');

    //TODO: clipping

    let gradient = null;
    switch (vector.type) {
      case 'ellipse':
        this.pdfDocument.ellipse(vector.x, vector.y, vector.r1, vector.r2);
        if (vector.linearGradient) {
          gradient = this.pdfDocument.linearGradient(vector.x - vector.r1, vector.y, vector.x + vector.r1, vector.y);
        }
        break;
      case 'rect':
        if (vector.r) {
          this.pdfDocument.roundedRect(vector.x, vector.y, vector.w, vector.h, vector.r);
        } else {
          this.pdfDocument.rect(vector.x, vector.y, vector.w, vector.h);
        }
        if (vector.linearGradient) {
          gradient = this.pdfDocument.linearGradient(vector.x, vector.y, vector.x + vector.w, vector.y);
        }
        break;
      case 'line':
        this.pdfDocument.moveTo(vector.x1, vector.y1);
        this.pdfDocument.lineTo(vector.x2, vector.y2);
        break;
      case 'polyline':
        if (vector.points.length === 0) {
          break;
        }
        this.pdfDocument.moveTo(vector.points[0].x, vector.points[0].y);
        for (let i = 1, l = vector.points.length; i < l; i++) {
          this.pdfDocument.lineTo(vector.points[i].x, vector.points[i].y);
        }
        if (vector.points.length > 1) {
          let p1 = vector.points[0];
          let pn = vector.points[vector.points.length - 1];
          if (vector.closePath || p1.x === pn.x && p1.y === pn.y) {
            this.pdfDocument.closePath();
          }
        }
        break;
      case 'path':
        this.pdfDocument.path(vector.d);
        break;
    }
    if (vector.linearGradient && gradient) {
      let step = 1 / (vector.linearGradient.length - 1);
      for (let i = 0; i < vector.linearGradient.length; i++) {
        gradient.stop(i * step, vector.linearGradient[i]);
      }
      vector.color = gradient;
    }
    let patternColor = this.pdfDocument.providePattern(vector.color);
    if (patternColor !== null) {
      vector.color = patternColor;
    }
    let fillOpacity = (0, _variableType.isNumber)(vector.fillOpacity) ? vector.fillOpacity : 1;
    let strokeOpacity = (0, _variableType.isNumber)(vector.strokeOpacity) ? vector.strokeOpacity : 1;
    if (vector.color && vector.lineColor) {
      this.pdfDocument.fillColor(this.pdfDocument.resolveColor(vector.color, 'black'), fillOpacity);
      this.pdfDocument.strokeColor(this.pdfDocument.resolveColor(vector.lineColor, 'black'), strokeOpacity);
      this.pdfDocument.fillAndStroke();
    } else if (vector.color) {
      this.pdfDocument.fillColor(this.pdfDocument.resolveColor(vector.color, 'black'), fillOpacity);
      this.pdfDocument.fill();
    } else {
      this.pdfDocument.strokeColor(this.pdfDocument.resolveColor(vector.lineColor, 'black'), strokeOpacity);
      this.pdfDocument.stroke();
    }
  }
  renderImage(image) {
    let opacity = (0, _variableType.isNumber)(image.opacity) ? image.opacity : 1;
    this.pdfDocument.opacity(opacity);
    if (image.cover) {
      const align = image.cover.align || 'center';
      const valign = image.cover.valign || 'center';
      const width = image.cover.width ? image.cover.width : image.width;
      const height = image.cover.height ? image.cover.height : image.height;
      this.pdfDocument.save();
      this.pdfDocument.rect(image.x, image.y, width, height).clip();
      this.pdfDocument.image(image.image, image.x, image.y, {
        cover: [width, height],
        align: align,
        valign: valign
      });
      this.pdfDocument.restore();
    } else {
      this.pdfDocument.image(image.image, image.x, image.y, {
        width: image._width,
        height: image._height
      });
    }
    if (image.link) {
      this.pdfDocument.link(image.x, image.y, image._width, image._height, image.link);
    }
    if (image.linkToPage) {
      this.pdfDocument.ref({
        Type: 'Action',
        S: 'GoTo',
        D: [image.linkToPage, 0, 0]
      }).end();
      this.pdfDocument.annotate(image.x, image.y, image._width, image._height, {
        Subtype: 'Link',
        Dest: [image.linkToPage - 1, 'XYZ', null, null, null]
      });
    }
    if (image.linkToDestination) {
      this.pdfDocument.goTo(image.x, image.y, image._width, image._height, image.linkToDestination);
    }
    if (image.linkToFile) {
      const attachment = this.pdfDocument.provideAttachment(image.linkToFile);
      this.pdfDocument.fileAnnotation(image.x, image.y, image._width, image._height, attachment,
      // add empty rectangle as file annotation appearance with the same size as the rendered image
      {
        AP: {
          N: {
            Type: 'XObject',
            Subtype: 'Form',
            FormType: 1,
            BBox: [image.x, image.y, image._width, image._height]
          }
        }
      });
    }
  }
  renderSVG(svg) {
    let options = {
      width: svg._width,
      height: svg._height,
      assumePt: true,
      useCSS: !(0, _variableType.isString)(svg.svg),
      ...svg.options
    };
    options.fontCallback = (family, bold, italic) => {
      let fontsFamily = family.split(',').map(f => f.trim().replace(/('|")/g, ''));
      let font = findFont(this.pdfDocument.fonts, fontsFamily, svg.font || 'Roboto');
      let fontFile = this.pdfDocument.getFontFile(font, bold, italic);
      if (fontFile === null) {
        let type = this.pdfDocument.getFontType(bold, italic);
        throw new Error(`Font '${font}' in style '${type}' is not defined in the font section of the document definition.`);
      }
      return fontFile;
    };
    (0, _svgToPdfkit.default)(this.pdfDocument, svg.svg, svg.x, svg.y, options);
    if (svg.link) {
      this.pdfDocument.link(svg.x, svg.y, svg._width, svg._height, svg.link);
    }
    if (svg.linkToPage) {
      this.pdfDocument.ref({
        Type: 'Action',
        S: 'GoTo',
        D: [svg.linkToPage, 0, 0]
      }).end();
      this.pdfDocument.annotate(svg.x, svg.y, svg._width, svg._height, {
        Subtype: 'Link',
        Dest: [svg.linkToPage - 1, 'XYZ', null, null, null]
      });
    }
    if (svg.linkToDestination) {
      this.pdfDocument.goTo(svg.x, svg.y, svg._width, svg._height, svg.linkToDestination);
    }
  }
  renderAttachment(attachment) {
    const file = this.pdfDocument.provideAttachment(attachment.attachment);
    const options = {};
    if (attachment.icon) {
      options.Name = attachment.icon;
    }
    this.pdfDocument.fileAnnotation(attachment.x, attachment.y, attachment._width, attachment._height, file, options);
  }
  beginClip(rect) {
    this.pdfDocument.save();
    this.pdfDocument.addContent(`${rect.x} ${rect.y} ${rect.width} ${rect.height} re`);
    this.pdfDocument.clip();
  }
  endClip() {
    this.pdfDocument.restore();
  }
  beginVerticalAlignment(item) {
    if (item.isCellContentMultiPage) {
      return;
    }
    switch (item.verticalAlignment) {
      case 'middle':
        this.pdfDocument.save();
        this.pdfDocument.translate(0, -(item.getNodeHeight() - item.getViewHeight()) / 2);
        break;
      case 'bottom':
        this.pdfDocument.save();
        this.pdfDocument.translate(0, -(item.getNodeHeight() - item.getViewHeight()));
        break;
    }
  }
  endVerticalAlignment(item) {
    if (item.isCellContentMultiPage) {
      return;
    }
    switch (item.verticalAlignment) {
      case 'middle':
      case 'bottom':
        this.pdfDocument.restore();
        break;
    }
  }
  renderWatermark(page) {
    let watermark = page.watermark;
    this.pdfDocument.fill(this.pdfDocument.resolveColor(watermark.color, 'black'));
    this.pdfDocument.opacity(watermark.opacity);
    this.pdfDocument.save();
    this.pdfDocument.rotate(watermark.angle, {
      origin: [this.pdfDocument.page.width / 2, this.pdfDocument.page.height / 2]
    });
    let x = this.pdfDocument.page.width / 2 - watermark._size.size.width / 2;
    let y = this.pdfDocument.page.height / 2 - watermark._size.size.height / 2;
    this.pdfDocument._font = watermark.font;
    this.pdfDocument.fontSize(watermark.fontSize);
    this.pdfDocument.text(watermark.text, x, y, {
      lineBreak: false
    });
    this.pdfDocument.restore();
  }
}
var _default = exports.default = Renderer;