view client/src/lib/mixins.js @ 3802:e8a950cf6c02 yworks-svg2pdf

Move Template loading and Imageprocessing to mixin Rationale: 1) Template loading is a process used by many components. As such it makes sense to parametrize the URL and centralize loading. 2) Imageprocessing has to be done after each template is loaded on the client As such it makes sense to centralize that. To make handling easier, each (1) and (2) is in an independend Promise to make chaining of calls easier to read.
author Thomas Junk <thomas.junk@intevation.de>
date Thu, 04 Jul 2019 10:57:43 +0200
parents 1399d31531f7
children 876dc90c2825
line wrap: on
line source

/* This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 *
 * Copyright (C) 2018, 2019 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * * Markus Kottländer <markus.kottlaender@intevation.de>
 * * Fadi Abbud <fadi.abbud@intevation.de>
 * * Bernhard Reiter <bernhard.reiter@intevation.de>
 */
import jsPDF from "jspdf-yworks";
import locale2 from "locale2";
import { mapState } from "vuex";
import { HTTP } from "@/lib/http";

export const sortTable = {
  data() {
    return {
      sortColumn: "",
      sortDirection: "ASC",
      pageSize: 20,
      page: 1
    };
  },
  methods: {
    sortTable(sorting) {
      this.sortColumn = sorting.sortColumn;
      this.sortDirection = sorting.sortDirection;
    }
  }
};

export const diagram = {
  methods: {
    getDimensions({ main, nav }) {
      //dimensions and margins
      const elem = document.querySelector("#" + this.containerId);
      const svgWidth = elem != null ? elem.clientWidth : 0;
      const svgHeight = elem != null ? elem.clientHeight : 0;
      const mainMargin = main || { top: 20, right: 20, bottom: 110, left: 80 };
      const navMargin = nav || {
        top: svgHeight - mainMargin.top - 65,
        right: 20,
        bottom: 30,
        left: 80
      };
      const width = Number(svgWidth) - mainMargin.left - mainMargin.right;
      const mainHeight = Number(svgHeight) - mainMargin.top - mainMargin.bottom;
      const navHeight = Number(svgHeight) - navMargin.top - navMargin.bottom;
      return { width, mainHeight, navHeight, mainMargin, navMargin };
    }
  }
};

export const pane = {
  computed: {
    paneId() {
      return this.$parent.pane.id;
    }
  }
};

export const templateLoader = {
  methods: {
    loadTemplates(url) {
      return new Promise((resolve, reject) => {
        HTTP.get(url, {
          headers: {
            "X-Gemma-Auth": localStorage.getItem("token"),
            "Content-type": "text/xml; charset=UTF-8"
          }
        })
          .then(response => {
            resolve(response);
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    prepareImages(elements) {
      /**
       * In order to render the images from the template, we need to convert
       * each image to dataURIs. Since this happens asynchronous,
       * we need to wrap each image into its own promise and only after all are
       * finished, we continue with the flow.
       */
      return new Promise(resolve => {
        const imageElementLoaders = elements.reduce((o, n, i) => {
          if (n.type === "image") {
            o.push(
              new Promise(resolve => {
                const image = new Image();
                image.onload = function() {
                  var canvas = document.createElement("canvas");
                  canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
                  canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
                  canvas.getContext("2d").drawImage(this, 0, 0);
                  resolve({
                    index: i,
                    url: canvas.toDataURL("image/png")
                  });
                };
                image.src = n.url;
              })
            );
          }
          return o;
        }, []);
        Promise.all(imageElementLoaders).then(values => {
          resolve(values);
        });
      });
    }
  }
};

export const pdfgen = {
  computed: {
    ...mapState("application", ["logoForPDF"]),
    ...mapState("user", ["user"])
  },
  methods: {
    gaugeInfo(selectedGauge) {
      // returns string with formatted gauge info
      return (
        selectedGauge.properties.objname +
        " (" +
        selectedGauge.id
          .split(".")[1]
          .replace(/[()]/g, "")
          .split(",")[3] +
        ")"
      );
    },
    generatePDF(params) {
      // creates a new jsPDF object into this.pdf.doc
      // will call functions that the calling context has to provide
      // as specified in the templateData
      let templateData = params["templateData"];
      let diagramTitle = params["diagramTitle"];

      this.pdf.doc = new jsPDF("l", "mm", templateData.properties.paperSize);
      // pdf width and height in millimeter (landscape)
      if (templateData.properties.paperSize === "a3") {
        this.pdf.width = 420;
        this.pdf.height = 297;
      } else {
        this.pdf.width = 297;
        this.pdf.height = 210;
      }
      // check the template elements
      if (templateData) {
        this.pdf.doc.setFont("linbiolinum", "normal");
        let defaultFontSize = 11,
          defaultColor = "black",
          defaultWidth = 70,
          defaultTextColor = "black",
          defaultBorderColor = "white",
          defaultBgColor = "white",
          defaultRounding = 2,
          defaultPadding = 2,
          defaultOffset = { x: 0, y: 0 };
        templateData.elements.forEach(e => {
          switch (e.type) {
            case "diagram": {
              this.addDiagram(e.position, e.offset || defaultOffset);
              break;
            }
            case "diagramlegend": {
              this.addDiagramLegend(
                e.position,
                e.offset || defaultOffset,
                e.color || defaultColor
              );
              break;
            }
            case "diagramtitle": {
              this.addDiagramTitle(
                e.position,
                e.offset || defaultOffset,
                e.fontsize || defaultFontSize,
                e.color || defaultColor,
                diagramTitle
              );
              break;
            }
            case "text": {
              this.addText(
                e.position,
                e.offset || defaultOffset,
                e.width || defaultWidth,
                e.fontsize || defaultFontSize,
                e.color || defaultTextColor,
                e.text || ""
              );
              break;
            }
            case "image": {
              this.addImage(
                e.url,
                e.format || "",
                e.position,
                e.offset || defaultOffset,
                e.width || 90,
                e.height || 60
              );
              break;
            }
            case "box": {
              this.addBox(
                e.position,
                e.offset || defaultOffset,
                e.width || 90,
                e.height || 60,
                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
                e.color || defaultBgColor,
                e.brcolor || defaultBorderColor
              );
              break;
            }
            case "textbox": {
              this.addTextBox(
                e.position,
                e.offset || defaultOffset,
                e.width,
                e.height,
                e.rounding === 0 || e.rounding ? e.rounding : defaultRounding,
                e.padding || defaultPadding,
                e.fontsize || defaultFontSize,
                e.color || defaultTextColor,
                e.background || defaultBgColor,
                e.text || "",
                e.brcolor || defaultBorderColor
              );
              break;
            }
          }
        });
      }
    },
    // add text at specific coordinates and do line breaks
    addText(position, offset, width, fontSize, color, text) {
      text = this.replacePlaceholders(text);
      // split the incoming string to an array, each element is a string of
      // words in a single line
      this.pdf.doc.setFontStyle("normal");
      this.pdf.doc.setTextColor(color);
      this.pdf.doc.setFontSize(fontSize);
      var textLines = this.pdf.doc.splitTextToSize(text, width);
      // x/y defaults to offset for topleft corner (normal x/y coordinates)
      let x = offset.x;
      let y = offset.y;
      // if position is on the right, x needs to be calculate with pdf width and
      // the size of the element
      if (["topright", "bottomright"].indexOf(position) !== -1) {
        x = this.pdf.width - offset.x - width;
      }
      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
        y = this.pdf.height - offset.y - this.getTextHeight(textLines.length);
      }
      this.pdf.doc.text(textLines, x, y, { baseline: "hanging" });
    },
    replacePlaceholders(text) {
      if (text.includes("{date}")) {
        text = text.replace("{date}", new Date().toLocaleString(locale2));
      }
      // get only day,month and year from the Date object
      if (text.includes("{date-minor}")) {
        var date = new Date();
        var dt =
          (date.getDate() < 10 ? "0" : "") +
          date.getDate() +
          "." +
          (date.getMonth() + 1 < 10 ? "0" : "") +
          (date.getMonth() + 1) +
          "." +
          date.getFullYear();
        text = text.replace("{date-minor}", dt.toLocaleString(locale2));
      }
      if (text.includes("{user}")) {
        text = text.replace("{user}", this.user);
      }
      return text;
    },
    addImage(url, format, position, offset, width, height) {
      let x = offset.x;
      let y = offset.y;
      if (["topright", "bottomright"].indexOf(position) !== -1) {
        x = this.pdf.width - offset.x - width;
      }
      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
        y = this.pdf.height - offset.y - height;
      }
      let image = new Image();
      if (url) {
        image.src = url;
      } else {
        if (this.logoForPDF) {
          image.src = this.logoForPDF;
        } else {
          image.src = "/img/gemma-logo-for-pdf.png";
        }
      }
      if (format === "") {
        let tmp = image.src.split(".");
        format = tmp[tmp.length - 1].toUpperCase();
      }
      this.pdf.doc.addImage(image, format, x, y, width, height);
    },
    // add text at specific coordinates with a background box
    addBox(position, offset, width, height, rounding, color, brcolor) {
      // x/y defaults to offset for topleft corner (normal x/y coordinates)
      let x = offset.x;
      let y = offset.y;

      // if position is on the right, x needs to be calculate with pdf width and
      // the size of the element
      if (["topright", "bottomright"].indexOf(position) !== -1) {
        x = this.pdf.width - offset.x - width;
      }
      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
        y = this.pdf.height - offset.y - height;
      }
      this.addRoundedBox(x, y, width, height, color, rounding, brcolor);
    },
    getTextHeight(numberOfLines) {
      // Return estimated height in mm.

      // FontSize is given in desktop publishing points defined as 1/72 inch.
      // aka 25.4 / 72 mm
      let fontSize = this.pdf.doc.getFontSize();
      let lineHeightFactor = 1.15; // default from jspdf-yworks 2.0.2
      if (typeof this.pdf.doc.getLineHeightFactor !== "undefined") {
        lineHeightFactor = this.pdf.doc.getLineHeightFactor();
      }
      return numberOfLines * fontSize * (25.4 / 72) * lineHeightFactor;
    },
    // title for diagram
    addDiagramTitle(position, offset, size, color, text) {
      let x = offset.x,
        y = offset.y;
      this.pdf.doc.setFontSize(size);
      this.pdf.doc.setFontStyle("bold");
      this.pdf.doc.setTextColor(color);
      let width =
        (this.pdf.doc.getStringUnitWidth(text) * size) / (72 / 25.6) + size / 2;
      // if position is on the right, x needs to be calculate with pdf width and
      // the size of the element
      if (["topright", "bottomright"].indexOf(position) !== -1) {
        x = this.pdf.width - offset.x - width;
      }
      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
        y = this.pdf.height - offset.y - this.getTextHeight(1);
      }
      this.pdf.doc.text(text, x, y, { baseline: "hanging" });
    },
    addRoundedBox(x, y, w, h, color, rounding, brcolor) {
      this.pdf.doc.setDrawColor(brcolor);
      this.pdf.doc.setFillColor(color);
      this.pdf.doc.roundedRect(x, y, w, h, rounding, rounding, "FD");
    },
    addTextBox(
      position,
      offset,
      width,
      height,
      rounding,
      padding,
      fontSize,
      color,
      background,
      text,
      brcolor
    ) {
      this.pdf.doc.setFontSize(fontSize);
      text = this.replacePlaceholders(text);

      if (!width) {
        width = this.pdf.doc.getTextWidth(text) + 2 * padding;
      }
      let textWidth = width - 2 * padding;
      if (!height) {
        let textLines = this.pdf.doc.splitTextToSize(text, textWidth);
        height = this.getTextHeight(textLines.length) + 2 * padding;
      }
      this.addBox(
        position,
        offset,
        width,
        height,
        rounding,
        background,
        brcolor
      );
      this.addText(
        position,
        { x: offset.x + padding, y: offset.y + padding },
        textWidth,
        fontSize,
        color,
        text
      );
    }
  }
};