diff client/src/components/Pdftool.vue @ 2218:631ca0412db9 pdf-export

adjusted positioning of pdf elements also cleaned up the code a little bit
author Markus Kottlaender <markus@intevation.de>
date Fri, 08 Feb 2019 16:16:04 +0100
parents 82867a69e10e
children 80de3787cdb3
line wrap: on
line diff
--- a/client/src/components/Pdftool.vue	Thu Feb 07 19:27:27 2019 +0100
+++ b/client/src/components/Pdftool.vue	Fri Feb 08 16:16:04 2019 +0100
@@ -138,6 +138,11 @@
         downloadType: "download",
         resolution: "120"
       },
+      pdf: {
+        doc: null,
+        width: null,
+        height: null
+      },
       logoImageForPDF: null, // a HTMLImageElement instance
       readyToGenerate: true // if the user is allowed to press the button
     };
@@ -185,16 +190,15 @@
         this.form.format,
         this.form.resolution
       );
-      var width, height;
 
       if (this.form.format !== "portrait") {
         // landscape, default
-        width = paperSizes[this.form.paperSize][0];
-        height = paperSizes[this.form.paperSize][1];
+        this.pdf.width = paperSizes[this.form.paperSize][0];
+        this.pdf.height = paperSizes[this.form.paperSize][1];
       } else {
         // switch width and height
-        width = paperSizes[this.form.paperSize][1];
-        height = paperSizes[this.form.paperSize][0];
+        this.pdf.width = paperSizes[this.form.paperSize][1];
+        this.pdf.height = paperSizes[this.form.paperSize][0];
       }
 
       // FUTURE: consider margins
@@ -203,8 +207,8 @@
       var pixelsPerMapMillimeter = this.form.resolution / 25.4;
       var mapSizeForPrint = [
         // in pixel
-        Math.round(width * pixelsPerMapMillimeter),
-        Math.round(height * pixelsPerMapMillimeter)
+        Math.round(this.pdf.width * pixelsPerMapMillimeter),
+        Math.round(this.pdf.height * pixelsPerMapMillimeter)
       ];
 
       // generate PDF and open it
@@ -222,12 +226,10 @@
       // extent should fit.
       var mapExtent = map.getView().calculateExtent(mapSize);
 
-      var pdf = new jsPDF(this.form.format, "mm", this.form.paperSize);
-      var northarrowSize = 3;
-      var self = this;
+      this.pdf.doc = new jsPDF(this.form.format, "mm", this.form.paperSize);
 
       // set a callback for after the next complete rendering of the map
-      map.once("rendercomplete", function(event) {
+      map.once("rendercomplete", event => {
         let canvas = event.context.canvas;
 
         // because we are using Web Mercator, a pixel represents
@@ -247,114 +249,72 @@
         console.log("scaleNominator = ", scaleNominator);
 
         var data = canvas.toDataURL("image/jpeg");
-        pdf.addImage(data, "JPEG", 0, 0, width, height);
-        //self.addScaleBar(pdf, width, height, scaleNominator);
-        self.addNorthArrow(pdf, 15, 9, northarrowSize);
-        //self.addPageInfo(pdf);
-        //self.addAboutBox(pdf, width, height);
+        this.pdf.doc.addImage(data, "JPEG", 0, 0);
 
-        if (self.getLayerByName("Bottleneck isolines").isVisible) {
-          self.addBottleneckInfo(pdf, 13, width, height);
-          self.addLegend(pdf, 14, width, height);
-        }
         if (template) {
-          template.elements.forEach(t => {
-            switch (t.type) {
-              case "image": {
-                if (t.imageUrl.length > 0) {
-                  pdf.addImage(
-                    t.imageUrl,
-                    t.imageType,
-                    t.x_coordinate,
-                    t.y_coordinate,
-                    t.imageWidth,
-                    t.imageHeight
-                  );
-                }
-                break;
-              }
-              case "scalebar": {
-                self.addScaleBar(
-                  pdf,
-                  width,
-                  height,
-                  scaleNominator,
-                  t.x_coordinate,
-                  t.y_coordinate
+          template.elements.forEach(e => {
+            switch (e.type) {
+              case "text": {
+                this.addText(
+                  e.position,
+                  e.offset,
+                  e.width,
+                  e.height,
+                  e.padding,
+                  e.fontSize,
+                  e.color,
+                  e.text
                 );
                 break;
               }
-              case "textbox": {
-                self.addText(
-                  pdf,
-                  t.x_coordinate,
-                  t.y_coordinate,
-                  t.elementSize,
-                  t.color,
-                  100,
-                  t.text
-                );
-                break;
-              }
-              case "docinfo": {
-                self.addPageInfo(
-                  pdf,
-                  t.x_coordinate,
-                  t.y_coordinate,
-                  t.elementWidth,
-                  t.elementHeight,
-                  t.textSize
+              case "image": {
+                this.addImage(
+                  e.url,
+                  e.format,
+                  e.position,
+                  e.offset,
+                  e.width,
+                  e.height,
+                  e.border
                 );
                 break;
               }
-              case "aboutbox": {
-                self.addAboutBox(pdf, width, height);
+              case "bottleneck": {
+                this.addBottleneckInfo(e.position, e.offset);
+                break;
+              }
+              case "legend": {
+                this.addLegend(e.position, e.offset);
                 break;
               }
-              /*  case "docinfo": {
-                self.addAboutBox(
-                  pdf,
-                  t.x_coordinate,
-                  t.y_coordinate,
-                  t.elementWidth,
-                  t.elementHeight
-                );
-              } */
+              case "scalebar": {
+                this.addScaleBar(scaleNominator, e.position, e.offset);
+                break;
+              }
+              case "northarrow": {
+                this.addNorthArrow(e.position, e.offset, e.size);
+                break;
+              }
             }
           });
 
-          pdf.save("map.pdf");
+          this.pdf.doc.save("map.pdf");
         } else {
-          self.addScaleBar(pdf, width, height, scaleNominator, 226.5, 204);
-          self.addPageInfo(pdf, 0, 0, 110, 8, 9);
-          self.addAboutBox(pdf, width, height);
-          pdf.save("map.pdf");
+          this.addScaleBar(scaleNominator, "bottomright", { x: 2, y: 2 });
+          this.pdf.doc.save("map.pdf");
         }
         // reset to original size
         map.setSize(mapSize);
         map.getView().fit(mapExtent, { size: mapSize });
 
         // as we are done: re-enable button
-        self.readyToGenerate = true;
+        this.readyToGenerate = true;
       });
 
       // trigger rendering
       this.prepareRendering(function() {
         map.setSize(mapSizeForPrint);
         map.getView().fit(mapExtent, { size: mapSizeForPrint });
-
-        /*
-      let a = document.createElement("a");
-      a.href = src; // need the generated PDF in here (as dataURL?)
-
-      if (this.form.downloadType === "download")
-        a.download = src.substr(src.lastIndexOf("/") + 1);
-      else a.target = "_blank";
-
-      document.body.appendChild(a);
-      a.click();
-      document.body.removeChild(a);
-      */
       });
     },
     prepareRendering(callback) {
@@ -371,17 +331,17 @@
         this.logoImageForPDF.src = "/img/gemma-logo-for-pdf.png";
       }
     },
-    addRoundedBox(doc, x, y, w, h) {
+    addRoundedBox(x, y, w, h) {
       // draws a rounded background box at (x,y) width x height
       // using jsPDF units
-      doc.setDrawColor(255, 255, 255);
-      doc.setFillColor(255, 255, 255);
-      doc.roundedRect(x, y, w, h, 3, 3, "FD");
+      this.pdf.doc.setDrawColor(255, 255, 255);
+      this.pdf.doc.setFillColor(255, 255, 255);
+      this.pdf.doc.roundedRect(x, y, w, h, 3, 3, "FD");
     },
-    addScaleBar(doc, docWidth, docHeight, scaleNominator, x, y) {
+    addScaleBar(scaleNominator, position, offset) {
       // scaleNominator is the x in 1:x of the map scale
 
-      // hardcode maximal width for now and place in lower right corner
+      // hardcode maximal width for now
       let maxWidth = 80; // in mm
 
       // reduce width until we'll find a nice number for printing
@@ -436,138 +396,268 @@
       }
 
       let size = (length * unitConversionFactor) / scaleNominator / 4;
+      let fullSize = size * 4;
 
-      //let x = docWidth - (size * 4 + 8);
-      //let y = docHeight - 6;
+      // x/y defaults to offset for topleft corner (normal x/y coordinates)
+      let x = offset.x;
+      let y = offset.y;
 
-      this.addRoundedBox(doc, x - 4, y - 4, size * 4 + 12, 10);
+      // 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 - fullSize - 8;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - 10;
+      }
+
+      // to give the outer white box 4mm padding
+      let scaleBarX = x + 4;
+      let scaleBarY = y + 5; // 5 because above the scalebar will be the numbers
+
+      // draw outer white box
+      this.addRoundedBox(x, y, fullSize + 8, 10);
 
-      doc.setDrawColor(0, 0, 0);
-      doc.setFillColor(0, 0, 0);
-      doc.rect(x, y, size, 1, "FD");
-      doc.setFillColor(255, 255, 255);
-      doc.setDrawColor(0, 0, 0);
-      doc.rect(x + size, y, size, 1, "FD");
-      doc.setFillColor(0, 0, 0);
-      doc.setDrawColor(0, 0, 0);
-      doc.rect(x + size * 2, y, size * 2, 1, "FD");
-      doc.setFontSize(5);
-      doc.text(x, y + 3, "0");
+      // draw first part of scalebar
+      this.pdf.doc.setDrawColor(0, 0, 0);
+      this.pdf.doc.setFillColor(0, 0, 0);
+      this.pdf.doc.rect(scaleBarX, scaleBarY, size, 1, "FD");
+
+      // draw second part of scalebar
+      this.pdf.doc.setDrawColor(0, 0, 0);
+      this.pdf.doc.setFillColor(255, 255, 255);
+      this.pdf.doc.rect(scaleBarX + size, scaleBarY, size, 1, "FD");
+
+      // draw third part of scalebar
+      this.pdf.doc.setDrawColor(0, 0, 0);
+      this.pdf.doc.setFillColor(0, 0, 0);
+      this.pdf.doc.rect(scaleBarX + size * 2, scaleBarY, size * 2, 1, "FD");
+
+      // draw numeric labels above scalebar
+      this.pdf.doc.setFontSize(5);
+      this.pdf.doc.text(scaleBarX, scaleBarY - 1, "0");
       // /4 and could give 2.5. We still round, because of floating point arith
-      doc.text(
-        x + size,
-        y + 3,
+      this.pdf.doc.text(
+        scaleBarX + size - 1,
+        scaleBarY - 1,
         (Math.round((length * 10) / 4) / 10).toString()
       );
-      doc.text(x + size * 2, y + 3, Math.round(length / 2).toString());
-      doc.text(x + size * 4, y + 3, Math.round(length).toString() + " " + unit);
+      this.pdf.doc.text(
+        scaleBarX + size * 2 - 2,
+        scaleBarY - 1,
+        Math.round(length / 2).toString()
+      );
+      this.pdf.doc.text(
+        scaleBarX + size * 4 - 4,
+        scaleBarY - 1,
+        Math.round(length).toString() + " " + unit
+      );
     },
 
-    addNorthArrow(doc, x1, y1, size) {
+    addNorthArrow(position, offset, size) {
+      // TODO: fix positioning
+      // x/y defaults to offset for topleft corner (normal x/y coordinates)
+      let x1 = offset.x;
+      let y1 = 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) {
+        x1 = this.pdf.width - offset.x - size;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y1 = this.pdf.height - offset.y - size;
+      }
+
       var y2 = y1 + size * 3;
       var x3 = x1 - size * 2;
       var y3 = y1 + size * 5;
       var x4 = x1 + size * 2;
-      //white triangle
-      doc.setFillColor(255, 255, 255);
-      doc.setDrawColor(255, 255, 255);
-      doc.triangle(x3 - 0.8, y3 + 1.2, x1, y1 - 1.2, x1, y2 + 0.6, "F");
-      doc.triangle(x1, y1 - 1.2, x1, y2 + 0.6, x4 + 0.8, y3 + 1.2, "F");
-      //north arrow
-      doc.setDrawColor(0, 0, 0);
-      doc.setFillColor(255, 255, 255);
-      doc.triangle(x3, y3, x1, y1, x1, y2, "FD");
-      doc.setFillColor(0, 0, 0);
-      doc.triangle(x1, y1, x1, y2, x4, y3, "FD");
-      doc.setFontSize(size * 3.1);
-      doc.setTextColor(255, 255, 255);
-      doc.setFontStyle("bold");
-      doc.text(size < 3 ? x1 - 0.5 : x1 - 1.3, y3 + 1, "N");
-      doc.setFontSize(size * 3);
-      doc.setTextColor(0, 0, 0);
-      doc.setFontStyle("normal");
-      doc.text(size < 3 ? x1 - 0.5 : x1 - 1.3, y3 + 1, "N");
+      // white triangle
+      this.pdf.doc.setFillColor(255, 255, 255);
+      this.pdf.doc.setDrawColor(255, 255, 255);
+      this.pdf.doc.triangle(
+        x3 - 0.8,
+        y3 + 1.2,
+        x1,
+        y1 - 1.2,
+        x1,
+        y2 + 0.6,
+        "F"
+      );
+      this.pdf.doc.triangle(
+        x1,
+        y1 - 1.2,
+        x1,
+        y2 + 0.6,
+        x4 + 0.8,
+        y3 + 1.2,
+        "F"
+      );
+
+      // north arrow
+      this.pdf.doc.setDrawColor(0, 0, 0);
+      this.pdf.doc.setFillColor(255, 255, 255);
+      this.pdf.doc.triangle(x3, y3, x1, y1, x1, y2, "FD");
+      this.pdf.doc.setFillColor(0, 0, 0);
+      this.pdf.doc.triangle(x1, y1, x1, y2, x4, y3, "FD");
+      this.pdf.doc.setFontSize(size * 3.1);
+      this.pdf.doc.setTextColor(255, 255, 255);
+      this.pdf.doc.setFontStyle("bold");
+      this.pdf.doc.text(size < 3 ? x1 - 0.5 : x1 - 1.3, y3 + 1, "N");
+      this.pdf.doc.setFontSize(size * 3);
+      this.pdf.doc.setTextColor(0, 0, 0);
+      this.pdf.doc.setFontStyle("normal");
+      this.pdf.doc.text(size < 3 ? x1 - 0.5 : x1 - 1.3, y3 + 1, "N");
     },
     // add some text at specific coordinates and determine how many wrolds in single line
-    addText(doc, postitionX, positionY, size, color, lineWidth, text) {
-      // split the incoming string to an array, each element is a string of words in a single line
-      var textLines = doc.splitTextToSize(text, lineWidth);
-      doc.setTextColor(color);
-      doc.setFontSize(size);
-      doc.text(postitionX, positionY, textLines);
-    },
-    addPageInfo(doc, x, y, width, height, textSize) {
-      this.addRoundedBox(doc, x, y, width, height);
-      let str =
-        this.$gettext("Date of publication:") +
-        " " +
-        new Date().toLocaleString(locale2) +
-        " " +
-        this.$gettext("– generated by:") +
-        " " +
-        this.user;
-      this.addText(doc, x + 5, y + 5, textSize, "black", 100, str);
-    },
-    addAboutBox(doc, docWidth, docHeight) {
-      let top = docHeight - 20;
-      this.addRoundedBox(doc, 0, top, 120, 20);
+    addText(position, offset, width, height, padding, fontSize, color, text) {
+      // 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 - 8;
+      }
+      if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
+        y = this.pdf.height - offset.y - 10;
+      }
+
+      this.addRoundedBox(x, y, width, height);
+
+      // replace placeholders
+      if (text.includes("{date}")) {
+        text = text.replace("{date}", new Date().toLocaleString(locale2));
+      }
+      if (text.includes("{user}")) {
+        text = text.replace("{user}", this.user);
+      }
 
-      let logoImage = this.logoImageForPDF;
-      let aspectRatio = logoImage.width / logoImage.height;
-      doc.addImage(logoImage, "PNG", 5, docHeight - 19, 110, 110 / aspectRatio);
-
-      let str =
-        "Dislaimer: Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua.";
-      this.addText(doc, 5, docHeight - 6, 8, "black", 115, str);
+      // split the incoming string to an array, each element is a string of words in a single line
+      var textLines = this.pdf.doc.splitTextToSize(text, width - 2 * padding);
+      this.pdf.doc.setTextColor(color);
+      this.pdf.doc.setFontSize(fontSize);
+      this.pdf.doc.text(x + padding, y + padding, textLines);
     },
-    addLegend(doc, fromTop, docWidth) {
-      // transforming into an HTMLImageElement only to find out
-      // the width x height of the legend image
-      // FUTURE: find a better way to get the width and height
-      let legendImage = new Image();
-      legendImage.src = this.isolinesLegendImgDataURL;
-      let aspectRatio = legendImage.width / legendImage.height;
+    addImage(url, format, position, offset, width, height, border) {
+      // x/y defaults to offset for topleft corner (normal x/y coordinates)
+      let x = offset.x;
+      let y = offset.y;
 
-      this.addRoundedBox(doc, docWidth - 54, fromTop, 54, 50 / aspectRatio + 4);
-      doc.addImage(
-        legendImage,
-        docWidth - 52,
-        fromTop + 2,
-        50,
-        50 / aspectRatio
+      // 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);
+
+      let image = new Image();
+      image.src = url;
+      this.pdf.doc.addImage(
+        image,
+        x + border,
+        y + border,
+        width - 2 * border,
+        height - 2 * border
       );
     },
-    addBottleneckInfo(doc, height, docWidth) {
-      this.addRoundedBox(doc, docWidth - 54, 0, 54, height);
-
-      console.log("Fontlist =", doc.getFontList());
-      doc.setFont("times", "normal");
-
-      let name, w, str;
+    addLegend(position, offset) {
+      if (
+        this.selectedBottleneck &&
+        this.getLayerByName("Bottleneck isolines").isVisible
+      ) {
+        // transforming into an HTMLImageElement only to find out
+        // the width x height of the legend image
+        // FUTURE: find a better way to get the width and height
+        let legendImage = new Image();
+        legendImage.src = this.isolinesLegendImgDataURL;
+        let aspectRatio = legendImage.width / legendImage.height;
+        let width = 54;
+        let height = width / aspectRatio;
+        let padding = 2;
 
-      doc.setFontStyle("italic");
-      name = this.$gettext("Bottleneck") + ": ";
-      w = doc.getTextWidth(name);
-      this.addText(doc, docWidth - 51, 4, 8, "black", 46, name);
-      doc.setFontStyle("bold");
-      str = this.selectedBottleneck;
-      this.addText(doc, docWidth - 51 + w, 4, 8, "black", 46, str);
+        // 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;
+        }
 
-      doc.setFontStyle("italic");
-      name = this.$gettext("Survey date") + ": ";
-      w = doc.getTextWidth(name);
-      this.addText(doc, docWidth - 51, 7.5, 8, "black", 46, name);
-      doc.setFontStyle("normal");
-      str = this.selectedSurvey.date_info;
-      this.addText(doc, docWidth - 51 + w, 7.5, 8, "black", 46, str);
+        this.addRoundedBox(x, y, width, height);
+        this.pdf.doc.addImage(
+          legendImage,
+          x + padding,
+          y + padding,
+          width - 2 * padding,
+          height - 2 * padding
+        );
+      }
+    },
+    addBottleneckInfo(position, offset) {
+      if (
+        this.selectedBottleneck &&
+        this.getLayerByName("Bottleneck isolines").isVisible
+      ) {
+        let width = 54;
+        let height = 13;
+        let padding = 5;
+
+        // 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;
+        }
 
-      doc.setFontStyle("italic");
-      name = this.$gettext("Ref gauge") + ": ";
-      w = doc.getTextWidth(name);
-      this.addText(doc, docWidth - 51, 11, 8, "black", 46, name);
-      doc.setFontStyle("normal");
-      str = this.selectedSurvey.gauge_objname;
-      this.addText(doc, docWidth - 51 + w, 11, 8, "black", 46, str);
+        this.addRoundedBox(x, y, width, height);
+
+        this.pdf.doc.setFont("times", "normal");
+        this.pdf.doc.setFontSize(9);
+
+        let str, w;
+
+        str = this.$gettext("Bottleneck") + ": ";
+        w = this.pdf.doc.getTextWidth(str);
+        this.pdf.doc.setFontStyle("italic");
+        this.pdf.doc.text(x + padding, y + padding, str);
+        str = this.selectedBottleneck;
+        this.pdf.doc.setFontStyle("bold");
+        this.pdf.doc.text(x + padding + w, y + padding, str);
+
+        str = this.$gettext("Survey date") + ": ";
+        w = this.pdf.doc.getTextWidth(str);
+        this.pdf.doc.setFontStyle("italic");
+        this.pdf.doc.text(x + padding, y + padding + 3, str);
+        str = this.selectedSurvey.date_info;
+        this.pdf.doc.setFontStyle("normal");
+        this.pdf.doc.text(x + padding + w, y + padding + 3, str);
+
+        str = this.$gettext("Ref gauge") + ": ";
+        w = this.pdf.doc.getTextWidth(str);
+        this.pdf.doc.setFontStyle("italic");
+        this.pdf.doc.text(x + padding, y + padding + 6, str);
+        str = this.selectedSurvey.gauge_objname;
+        this.pdf.doc.setFontStyle("normal");
+        this.pdf.doc.text(x + padding + w, y + padding + 6, str);
+      }
     }
   },
   mounted() {