changeset 2224:5176ea7fbc53 pdf-export

merge default in pdf-branch
author Markus Kottlaender <markus@intevation.de>
date Tue, 12 Feb 2019 11:11:32 +0100
parents 85142493096c (diff) 35c56ace42b9 (current diff)
children bc1780dd7cd1
files client/src/assets/application.scss client/src/components/Pdftool.vue client/src/main.js client/src/store/map.js
diffstat 8 files changed, 909 insertions(+), 394 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/assets/application.scss	Tue Feb 12 10:50:03 2019 +0100
+++ b/client/src/assets/application.scss	Tue Feb 12 11:11:32 2019 +0100
@@ -99,16 +99,13 @@
 }
 
 .popup {
-  position: absolute;
-  top: 40%;
-  left: 50%;
-  margin-left: -150px;
   width: 300px;
   max-width: 300px;
+  @extend %fully-centered;
 }
 
 .popup.show {
-  margin: 0.5rem 0 0 -150px;
+  margin: 0.5rem 0 0 0;
   max-height: 999px;
 }
 
@@ -133,6 +130,14 @@
   font-weight: bold;
 }
 
+.list-fade-enter-active, .list-fade-leave-active {
+  transition: transform .3s;
+}
+.list-fade-enter, .list-fade-leave-to {
+  opacity: 0;
+  transform: translateY(20px);
+}
+
 .pointer {
   cursor: pointer;
 }
--- a/client/src/components/Pdftool.vue	Tue Feb 12 10:50:03 2019 +0100
+++ b/client/src/components/Pdftool.vue	Tue Feb 12 11:11:32 2019 +0100
@@ -21,19 +21,15 @@
           v-model="form.template"
           class="form-control d-block mb-2 w-100 font-weight-bold"
         >
-          <option :value="null"><translate>Chose preset</translate></option>
           <option
-            v-for="template in templates"
-            :value="template.name"
+            v-for="template in pdfTemplates"
+            :value="template"
             :key="template.name"
           >
             <translate>{{ template.name }}</translate>
           </option>
         </select>
-        <hr class="mb-1" />
-        <small class="text-muted"><translate>Format</translate></small>
         <select
-          @change="compareFormWithTemplates"
           v-model="form.format"
           class="form-control form-control-sm d-block mb-2 w-100"
         >
@@ -42,9 +38,7 @@
         </select>
         <div class="d-flex">
           <div class="flex-fill mr-2">
-            <small class="text-muted"><translate>Resolution</translate></small>
             <select
-              @change="compareFormWithTemplates"
               v-model="form.resolution"
               class="form-control form-control-sm mb-2 d-block w-100"
             >
@@ -54,14 +48,12 @@
             </select>
           </div>
           <div class="flex-fill ml-2">
-            <small class="text-muted"><translate>Size</translate></small>
             <select
-              @change="compareFormWithTemplates"
               v-model="form.paperSize"
               class="form-control form-control-sm mb-2 d-block w-100"
             >
-              <option value="a3"><translate>ISO A3</translate></option>
-              <option value="a4"><translate>ISO A4</translate></option>
+              <option value="a4"><translate>A4</translate></option>
+              <option value="a3"><translate>A3</translate></option>
             </select>
           </div>
         </div>
@@ -137,32 +129,19 @@
         format: "landscape",
         paperSize: "a4",
         downloadType: "download",
-        resolution: "120"
+        resolution: "80"
       },
-      templates: [
-        {
-          name: "Template 1",
-          properties: {
-            format: "landscape",
-            resolution: "80",
-            paperSize: "a4"
-          }
-        },
-        {
-          name: "Template 2",
-          properties: {
-            format: "portrait",
-            resolution: "120",
-            paperSize: "a3"
-          }
-        }
-      ],
+      pdf: {
+        doc: null,
+        width: null,
+        height: null
+      },
       logoImageForPDF: null, // a HTMLImageElement instance
       readyToGenerate: true // if the user is allowed to press the button
     };
   },
   computed: {
-    ...mapState("application", ["showPdfTool", "logoForPDF"]),
+    ...mapState("application", ["showPdfTool", "logoForPDF", "pdfTemplates"]),
     ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"]),
     ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL"]),
     ...mapGetters("map", ["getLayerByName"]),
@@ -172,28 +151,15 @@
     // When a template is chosen from the dropdown, its propoerties are
     // applied to the rest of the form.
     applyTemplateToForm() {
-      let template = this.templates.find(t => t.name === this.form.template);
-      if (template) {
-        this.form.format = template.properties.format;
-        this.form.paperSize = template.properties.paperSize;
-        this.form.resolution = template.properties.resolution;
+      if (this.form.template) {
+        this.form.format = this.form.template.properties.format;
+        this.form.paperSize = this.form.template.properties.paperSize;
+        this.form.resolution = this.form.template.properties.resolution;
       }
     },
-    // If there's a template that matches all the form values, this template
-    // will be set in the dropdown.
-    compareFormWithTemplates() {
-      this.form.template = null;
-      this.templates.forEach(t => {
-        if (
-          this.form.format === t.properties.format &&
-          this.form.paperSize === t.properties.paperSize &&
-          this.form.resolution === t.properties.resolution
-        ) {
-          this.form.template = t.name;
-        }
-      });
-    },
     download() {
+      let template = this.form.template;
+
       // disable button while working on it
       this.readyToGenerate = false;
 
@@ -203,16 +169,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
@@ -221,8 +186,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
@@ -240,12 +205,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
@@ -265,43 +228,117 @@
         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, 15.5, width, height);
-          self.addLegend(pdf, 16.5, width, height);
+        if (template) {
+          let defaultFontSize = 10,
+            defaultRounding = 0,
+            defaultTextColor = "black",
+            defaultBgColor = "white",
+            defaultPadding = 3,
+            defaultOffset = { x: 0, y: 0 };
+          template.elements.forEach(e => {
+            switch (e.type) {
+              case "text": {
+                this.addText(
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.width,
+                  e.rounding || defaultRounding,
+                  e.fontSize || defaultFontSize,
+                  e.color || defaultTextColor,
+                  e.text
+                );
+                break;
+              }
+              case "box": {
+                this.addBox(
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.width,
+                  e.height,
+                  e.rounding || defaultRounding,
+                  e.color || defaultBgColor
+                );
+                break;
+              }
+              case "textbox": {
+                this.addTextBox(
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.width,
+                  e.height,
+                  e.rounding || defaultRounding,
+                  e.padding || defaultPadding,
+                  e.fontSize || defaultFontSize,
+                  e.color || defaultTextColor,
+                  e.background || defaultBgColor,
+                  e.text
+                );
+                break;
+              }
+              case "image": {
+                this.addImage(
+                  e.url,
+                  e.format,
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.width,
+                  e.height
+                );
+                break;
+              }
+              case "bottleneck": {
+                this.addBottleneckInfo(
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.rounding || defaultRounding,
+                  e.color || defaultTextColor
+                );
+                break;
+              }
+              case "legend": {
+                this.addLegend(
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.rounding || defaultRounding
+                );
+                break;
+              }
+              case "scalebar": {
+                this.addScaleBar(
+                  scaleNominator,
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.rounding || defaultRounding
+                );
+                break;
+              }
+              case "northarrow": {
+                this.addNorthArrow(
+                  e.position,
+                  e.offset || defaultOffset,
+                  e.size
+                );
+                break;
+              }
+            }
+          });
+
+          this.pdf.doc.save("map.pdf");
         }
-
-        pdf.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) {
@@ -318,17 +355,111 @@
         this.logoImageForPDF.src = "/img/gemma-logo-for-pdf.png";
       }
     },
-    addRoundedBox(doc, x, y, w, h) {
+    addRoundedBox(x, y, w, h, color, rounding) {
       // 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(color);
+      this.pdf.doc.setFillColor(color);
+      this.pdf.doc.roundedRect(x, y, w, h, rounding, rounding, "FD");
+    },
+    // add some text at specific coordinates and determine how many wrolds in single line
+    addText(position, offset, width, rounding, 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.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" });
+    },
+    addBox(position, offset, width, height, rounding, color) {
+      // 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);
     },
-    addScaleBar(doc, docWidth, docHeight, scaleNominator) {
+    // add some text at specific coordinates with a background box
+    addTextBox(
+      position,
+      offset,
+      width,
+      height,
+      rounding,
+      padding,
+      fontSize,
+      color,
+      background,
+      text
+    ) {
+      if (!width) {
+        width = this.pdf.width - offset.x;
+      }
+      let textWidth = width - 2 * padding;
+      if (!height) {
+        this.pdf.doc.setFontSize(fontSize);
+        text = this.replacePlaceholders(text);
+        let textLines = this.pdf.doc.splitTextToSize(text, textWidth);
+        height = this.getTextHeight(textLines.length) + 2 * padding;
+      }
+
+      this.addBox(position, offset, width, height, rounding, background);
+      this.addText(
+        position,
+        { x: offset.x + padding, y: offset.y + padding },
+        textWidth,
+        rounding,
+        fontSize,
+        color,
+        text
+      );
+    },
+    addImage(url, format, position, offset, width, height) {
+      // 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;
+      }
+
+      let image = new Image();
+      image.src = url;
+      this.pdf.doc.addImage(image, x, y, width, height);
+    },
+    addScaleBar(scaleNominator, position, offset, rounding) {
       // 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
@@ -383,154 +514,234 @@
       }
 
       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, "white", rounding);
 
-      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.setTextColor("black");
+      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(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;
 
-    addNorthArrow(doc, x1, y1, size) {
+      // 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) {
-      this.addRoundedBox(doc, 0, 0, 110, 8);
-      let str =
-        this.$gettext("Date of publication:") +
-        " " +
-        new Date().toLocaleString(locale2) +
-        " " +
-        this.$gettext("– generated by:") +
-        " " +
-        this.user;
-      this.addText(doc, 5, 5, 9, "black", 100, str);
-    },
-    addAboutBox(doc, docWidth, docHeight) {
-      let top = docHeight - 20;
-      this.addRoundedBox(doc, 0, top, 120, 20);
+    addLegend(position, offset, rounding) {
+      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;
+
+        // x/y defaults to offset for topleft corner (normal x/y coordinates)
+        let x = offset.x;
+        let y = offset.y;
 
-      let logoImage = this.logoImageForPDF;
-      let aspectRatio = logoImage.width / logoImage.height;
-      doc.addImage(logoImage, "PNG", 5, docHeight - 19, 110, 110 / 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;
+        }
 
-      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);
+        this.addRoundedBox(x, y, width, height, "white", rounding);
+        this.pdf.doc.addImage(
+          legendImage,
+          x + padding,
+          y + padding,
+          width - 2 * padding,
+          height - 2 * padding
+        );
+      }
     },
-    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;
+    addBottleneckInfo(position, offset, rounding, color) {
+      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;
 
-      this.addRoundedBox(doc, docWidth - 54, fromTop, 54, 50 / aspectRatio + 4);
-      doc.addImage(
-        legendImage,
-        docWidth - 52,
-        fromTop + 2,
-        50,
-        50 / aspectRatio
-      );
-    },
-    addBottleneckInfo(doc, height, docWidth) {
-      this.addRoundedBox(doc, docWidth - 54, 0, 54, height);
+        // 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, "white", rounding);
 
-      console.log("Fontlist =", doc.getFontList());
-      doc.setFont("times", "normal");
+        this.pdf.doc.setFont("times", "normal");
+        this.pdf.doc.setFontSize(9);
+        this.pdf.doc.setTextColor(color);
+
+        let str, w;
 
-      let name, w, str;
+        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);
 
-      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);
-
-      let survey = this.selectedSurvey;
-
-      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 = survey.date_info;
-      this.addText(doc, docWidth - 51 + w, 7.5, 8, "black", 46, 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);
 
-      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 = survey.gauge_objname;
-      this.addText(doc, docWidth - 51 + w, 11, 8, "black", 46, str);
-
-      doc.setFontStyle("italic");
-      name = this.$gettext("Depth relativ to") + ": ";
-      w = doc.getTextWidth(name);
-      this.addText(doc, docWidth - 51, 14.5, 8, "black", 46, name);
-      doc.setFontStyle("normal");
-      str = survey.depth_reference + " = ";
-      if (survey.hasOwnProperty("waterlevel_value")) {
-        str += survey.waterlevel_value + " cm";
-      } else {
-        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);
+      }
+    },
+    replacePlaceholders(text) {
+      if (text.includes("{date}")) {
+        text = text.replace("{date}", new Date().toLocaleString(locale2));
       }
-      this.addText(doc, docWidth - 51 + w, 14.5, 8, "black", 46, str);
+      if (text.includes("{user}")) {
+        text = text.replace("{user}", this.user);
+      }
+      return text;
+    },
+    getTextHeight(numberOfLines) {
+      return (
+        numberOfLines *
+        ((this.pdf.doc.getFontSize() * 25.4) / parseInt(this.form.resolution)) *
+        this.pdf.doc.getLineHeightFactor()
+      );
     }
+  },
+  mounted() {
+    this.$store.dispatch("application/loadPdfTemplates").then(() => {
+      this.form.template = this.pdfTemplates[0];
+    });
   }
 };
 </script>
--- a/client/src/components/Systemconfiguration.vue	Tue Feb 12 10:50:03 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <Spacer></Spacer>
-    <div class="card sysconfig mt-3 shadow-xs">
-      <h6
-        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-      >
-        <font-awesome-icon icon="wrench" class="mr-2"></font-awesome-icon>
-        <translate class="headline">Systemconfiguration</translate>
-      </h6>
-      <div class="card-body config">
-        <section class="configsection">
-          <h4 class="card-title">
-            <translate>Bottleneck Areas stroke-color</translate>
-          </h4>
-          <compact-picker v-model="strokeColor" />
-        </section>
-        <section>
-          <h4 class="card-title">
-            <translate>Bottleneck Areas fill-color</translate>
-          </h4>
-          <chrome-picker v-model="fillColor" />
-        </section>
-        <div class="sendbutton">
-          <a @click.prevent="submit" class="btn btn-info text-white">
-            <translate>Send</translate>
-          </a>
-        </div>
-      </div>
-      <!-- card-body -->
-    </div>
-  </div>
-</template>
-
-<style scoped lang="scss">
-.config {
-  text-align: left;
-}
-
-.configsection {
-  margin-bottom: $large-offset;
-}
-
-.sendbutton {
-  position: absolute;
-  right: $offset;
-  bottom: $offset;
-}
-
-.inputs {
-  margin-left: auto;
-  margin-right: auto;
-}
-
-.sysconfig {
-  margin-right: $offset;
-  width: 100%;
-  height: 100%;
-}
-</style>
-
-<script>
-/* 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 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Bernhard Reiter <bernhard@intevation.de>
- */
-import { Chrome } from "vue-color";
-import { Compact } from "vue-color";
-
-import { HTTP } from "@/lib/http";
-import { displayError } from "@/lib/errors.js";
-import { mapState } from "vuex";
-
-export default {
-  name: "systemconfiguration",
-  data() {
-    return {
-      sent: false,
-      strokeColor: { r: 0, g: 0, b: 0, a: 1.0 },
-      fillColor: { r: 0, g: 0, b: 0, a: 1.0 },
-      currentConfig: null
-    };
-  },
-  components: {
-    "chrome-picker": Chrome,
-    "compact-picker": Compact,
-    Spacer: () => import("./Spacer")
-  },
-  computed: {
-    ...mapState("application", ["showSidebar"])
-  },
-  methods: {
-    submit() {
-      HTTP.put("/system/style/Bottlenecks/stroke", this.strokeColor.rgba, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then()
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-
-      HTTP.put("/system/style/Bottlenecks/fill", this.fillColor.rgba, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then()
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  mounted() {
-    HTTP.get("/system/style/Bottlenecks/stroke", {
-      headers: {
-        "X-Gemma-Auth": localStorage.getItem("token"),
-        "Content-type": "application/json"
-      }
-    })
-      .then(response => {
-        this.strokeColor = response.data.colour;
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
-
-    HTTP.get("/system/style/Bottlenecks/fill", {
-      headers: {
-        "X-Gemma-Auth": localStorage.getItem("token"),
-        "Content-type": "application/json"
-      }
-    })
-      .then(response => {
-        this.fillColor = response.data.colour;
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
-  }
-};
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/systemconfiguration/PDFTemplates.vue	Tue Feb 12 11:11:32 2019 +0100
@@ -0,0 +1,142 @@
+<template>
+  <div class="d-flex flex-column mt-4">
+    <div class="d-flex flex-row justify-content-between">
+      <h5><translate>PDF-Templates</translate></h5>
+      <input
+        @change="upload"
+        id="uploadPDFTemplates"
+        ref="uploadPDFTemplates"
+        type="file"
+        style="visibility:hidden"
+      />
+      <button
+        class="btn btn-sm btn-info"
+        @click="$refs.uploadPDFTemplates.click()"
+      >
+        <font-awesome-icon icon="spinner" class="fa-spin" v-if="uploading" />
+        <font-awesome-icon icon="plus" v-else />
+      </button>
+    </div>
+    <div class="d-flex mt-1">
+      <table class="table table-sm">
+        <thead>
+          <tr>
+            <th>Name</th>
+            <th>Description</th>
+            <th>Date</th>
+            <th>Country</th>
+            <th></th>
+          </tr>
+        </thead>
+        <transition-group name="list-fade" tag="tbody">
+          <tr v-for="template in pdfTemplates" :key="template.name">
+            <td>{{ template.name }}</td>
+            <td>{{ template.description }}</td>
+            <td>{{ template.date }}</td>
+            <td></td>
+            <td class="text-right">
+              <button class="btn btn-sm btn-info mr-2">
+                <font-awesome-icon icon="download" />
+              </button>
+              <button
+                class="btn btn-sm btn-danger"
+                @click="
+                  showDeleteTemplatePrompt = true;
+                  templateToDelete = template;
+                "
+              >
+                <font-awesome-icon icon="trash" />
+              </button>
+            </td>
+          </tr>
+        </transition-group>
+      </table>
+    </div>
+
+    <div
+      :class="[
+        'box popup ui-element rounded bg-white',
+        { show: showDeleteTemplatePrompt }
+      ]"
+    >
+      <div>
+        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+          <font-awesome-icon icon="trash" class="mr-2"></font-awesome-icon>
+          <translate>Delete PDF Template</translate>
+          <font-awesome-icon
+            icon="times"
+            class="ml-auto text-muted"
+            @click="showDeleteTemplatePrompt = false"
+          ></font-awesome-icon>
+        </h6>
+        <div class="p-3 text-left">
+          <translate class="text-center d-block">
+            Do you really want to delete the following template:
+          </translate>
+          <h5 class="mt-3 text-center">{{ templateToDelete.name }}</h5>
+        </div>
+        <div
+          class="py-2 px-3 border-top d-flex align-items-center justify-content-between"
+        >
+          <button
+            class="btn btn-sm btn-warning"
+            @click="showDeleteTemplatePrompt = false"
+          >
+            no
+          </button>
+          <button class="btn btn-sm btn-info" @click="remove(templateToDelete)">
+            yes
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* 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 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "pdftemplates",
+  data() {
+    return {
+      uploading: false,
+      templateToDelete: "",
+      showDeleteTemplatePrompt: false
+    };
+  },
+  computed: {
+    ...mapState("application", ["pdfTemplates"])
+  },
+  methods: {
+    upload() {
+      this.uploading = true;
+      this.$store
+        .dispatch(
+          "application/uploadPDFTemplates",
+          this.$refs.uploadPDFTemplates.files
+        )
+        .then(() => {
+          this.uploading = false;
+        });
+    },
+    remove(template) {
+      this.showDeleteTemplatePrompt = false;
+      this.$store.dispatch("application/removePDFTemplate", template);
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/systemconfiguration/Systemconfiguration.vue	Tue Feb 12 11:11:32 2019 +0100
@@ -0,0 +1,159 @@
+<template>
+  <div class="d-flex flex-row">
+    <Spacer></Spacer>
+    <div class="card sysconfig mt-3 shadow-xs">
+      <h6
+        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+      >
+        <font-awesome-icon icon="wrench" class="mr-2"></font-awesome-icon>
+        <translate class="headline">Systemconfiguration</translate>
+      </h6>
+      <div class="card-body text-left">
+        <h5 class="border-bottom pb-3 mb-3">
+          <translate>Color settings</translate>
+        </h5>
+        <div class="d-flex">
+          <div>
+            <h6 class="card-title">
+              <translate>Bottleneck Areas fill-color</translate>
+            </h6>
+            <chrome-picker v-model="fillColor" />
+          </div>
+          <div class="ml-4">
+            <h6 class="card-title">
+              <translate>Bottleneck Areas stroke-color</translate>
+            </h6>
+            <compact-picker v-model="strokeColor" />
+          </div>
+        </div>
+        <div class="mt-4">
+          <a @click.prevent="submit" class="btn btn-info text-white">
+            <translate>Send</translate>
+          </a>
+        </div>
+        <PDFTemplates />
+      </div>
+      <!-- card-body -->
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.sysconfig {
+  margin-right: $offset;
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<script>
+/* 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 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Bernhard Reiter <bernhard@intevation.de>
+ */
+import { Chrome } from "vue-color";
+import { Compact } from "vue-color";
+
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors.js";
+import { mapState } from "vuex";
+
+export default {
+  name: "systemconfiguration",
+  data() {
+    return {
+      sent: false,
+      strokeColor: { r: 0, g: 0, b: 0, a: 1.0 },
+      fillColor: { r: 0, g: 0, b: 0, a: 1.0 },
+      currentConfig: null
+    };
+  },
+  components: {
+    "chrome-picker": Chrome,
+    "compact-picker": Compact,
+    Spacer: () => import("../Spacer"),
+    PDFTemplates: () => import("./PDFTemplates")
+  },
+  computed: {
+    ...mapState("application", ["showSidebar"])
+  },
+  methods: {
+    submit() {
+      HTTP.put("/system/style/Bottlenecks/stroke", this.strokeColor.rgba, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then()
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+
+      HTTP.put("/system/style/Bottlenecks/fill", this.fillColor.rgba, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then()
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  mounted() {
+    HTTP.get("/system/style/Bottlenecks/stroke", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "application/json"
+      }
+    })
+      .then(response => {
+        this.strokeColor = response.data.colour;
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+
+    HTTP.get("/system/style/Bottlenecks/fill", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "application/json"
+      }
+    })
+      .then(response => {
+        this.fillColor = response.data.colour;
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  }
+};
+</script>
--- a/client/src/main.js	Tue Feb 12 10:50:03 2019 +0100
+++ b/client/src/main.js	Tue Feb 12 11:11:32 2019 +0100
@@ -43,6 +43,7 @@
   faClock,
   faCloudUploadAlt,
   faCopy,
+  faDownload,
   faDrawPolygon,
   faExclamationTriangle,
   faEye,
@@ -96,6 +97,7 @@
   faClock,
   faCloudUploadAlt,
   faCopy,
+  faDownload,
   faDrawPolygon,
   faExclamationTriangle,
   faEye,
--- a/client/src/router.js	Tue Feb 12 10:50:03 2019 +0100
+++ b/client/src/router.js	Tue Feb 12 11:11:32 2019 +0100
@@ -65,7 +65,8 @@
     {
       path: "/systemconfiguration",
       name: "systemconfiguration",
-      component: () => import("./components/Systemconfiguration.vue"),
+      component: () =>
+        import("./components/systemconfiguration/Systemconfiguration.vue"),
       meta: {
         requiresAuth: true
       },
--- a/client/src/store/application.js	Tue Feb 12 10:50:03 2019 +0100
+++ b/client/src/store/application.js	Tue Feb 12 11:11:32 2019 +0100
@@ -15,6 +15,7 @@
  */
 
 import { version } from "../../package.json";
+// import { HTTP } from "../lib/http";
 
 // initial state
 const init = () => {
@@ -22,6 +23,7 @@
     appTitle: process.env.VUE_APP_TITLE,
     secondaryLogo: process.env.VUE_APP_SECONDARY_LOGO_URL,
     logoForPDF: process.env.VUE_APP_LOGO_FOR_PDF_URL,
+    pdfTemplates: [],
     showSidebar: false,
     showUsermenu: false,
     showSplitscreen: false,
@@ -103,6 +105,170 @@
     },
     searchQuery: (state, searchQuery) => {
       state.searchQuery = searchQuery;
+    },
+    pdfTemplates: (state, pdfTemplates) => {
+      state.pdfTemplates = pdfTemplates;
+    }
+  },
+  actions: {
+    loadPdfTemplates({ commit }) {
+      return new Promise(resolve => {
+        // pretend we do something async
+        setTimeout(function() {
+          commit("pdfTemplates", [
+            {
+              name: "Default Template",
+              properties: {
+                format: "landscape",
+                resolution: "120",
+                paperSize: "a4"
+              },
+              elements: [
+                {
+                  type: "textbox",
+                  position: "bottomleft",
+                  offset: { x: 2, y: 2 },
+                  width: 100,
+                  fontSize: 8,
+                  text: "Date of publication: {date} - generated by: {user}"
+                },
+                {
+                  type: "bottleneck",
+                  position: "topleft",
+                  offset: { x: 2, y: 2 }
+                },
+                {
+                  type: "legend",
+                  position: "topleft",
+                  offset: { x: 2, y: 18 }
+                },
+                {
+                  type: "scalebar",
+                  position: "bottomright",
+                  offset: { x: 2, y: 2 }
+                },
+                {
+                  type: "northarrow",
+                  position: "topright",
+                  offset: { x: 10, y: 5 },
+                  size: 2
+                }
+              ]
+            },
+            {
+              name: "Minimum Template",
+              properties: {
+                format: "landscape",
+                resolution: "120",
+                paperSize: "a4"
+              },
+              elements: [
+                {
+                  type: "textbox",
+                  position: "bottomleft",
+                  text: "Lorem ipsum dolor"
+                }
+              ]
+            },
+            {
+              name: "Full Template",
+              properties: {
+                format: "landscape",
+                resolution: "80",
+                paperSize: "a4"
+              },
+              elements: [
+                {
+                  type: "box",
+                  position: "topleft",
+                  offset: { x: 20, y: 2 },
+                  width: 60,
+                  height: 25,
+                  color: "green"
+                },
+                {
+                  type: "text",
+                  position: "topleft",
+                  offset: { x: 23, y: 5 },
+                  width: 56,
+                  fontSize: 10,
+                  color: "greenyellow",
+                  text:
+                    "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."
+                },
+                {
+                  type: "textbox",
+                  position: "bottomleft",
+                  offset: { x: 2, y: 2 },
+                  width: 90,
+                  padding: 3,
+                  fontSize: 7,
+                  color: "gray",
+                  background: "white",
+                  text: "Date of publication: {date} - generated by: {user}"
+                },
+                {
+                  type: "image",
+                  format: "PNG",
+                  url:
+                    "data:image/png;base64,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",
+                  position: "topleft",
+                  offset: { x: 2, y: 60 },
+                  width: 50,
+                  height: 23,
+                  border: 2
+                },
+                {
+                  type: "bottleneck",
+                  position: "topright",
+                  offset: { x: 2, y: 2 }
+                },
+                {
+                  type: "legend",
+                  position: "topright",
+                  offset: { x: 2, y: 18 }
+                },
+                {
+                  type: "scalebar",
+                  position: "bottomright",
+                  offset: { x: 2, y: 2 }
+                },
+                {
+                  type: "northarrow",
+                  position: "topleft",
+                  offset: { x: 8, y: 4 },
+                  size: 2
+                }
+              ]
+            }
+          ]);
+          resolve();
+        }, 500);
+      });
+    },
+    uploadPDFTemplates({ state, commit }, files) {
+      return new Promise((resolve, reject) => {
+        setTimeout(() => {
+          const reader = new FileReader();
+          reader.onload = event => {
+            let templates = state.pdfTemplates;
+            templates.push(JSON.parse(event.target.result));
+            commit("pdfTemplates", templates);
+            // TODO: send template to server
+          };
+          reader.onerror = error => reject(error);
+          reader.readAsText(files[0]);
+          resolve();
+        }, 1000);
+      });
+    },
+    removePDFTemplate({ state, commit }, template) {
+      let templates = state.pdfTemplates;
+      let removeIndex = templates.findIndex(t => t.name === template.name);
+      if (removeIndex !== -1) {
+        templates.splice(removeIndex, 1);
+        commit("pdfTemplates", templates);
+      }
     }
   }
 };