Mercurial > gemma
view client/src/components/Pdftool.vue @ 1887:3ed036adc80f dev-pdf-generation
client: pdf-gen: fix scale calculation
* Change scale calculation to use ol.proj.getPointResolution() at
the center of the current view to make up for different point dimensions
if you are not on the equator for the web mercator projection. This is
also how the scaleline control of OpenLayers does it.
author | Bernhard Reiter <bernhard@intevation.de> |
---|---|
date | Thu, 17 Jan 2019 21:48:11 +0100 |
parents | 20fe31b4dd5d |
children | c78efb1ddb02 |
line wrap: on
line source
<template> <div :class="[ 'box ui-element rounded bg-white text-nowrap', { expanded: showPdfTool } ]" > <div style="width: 20rem"> <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center"> <font-awesome-icon icon="file-pdf" class="mr-2"></font-awesome-icon> <translate>Generate PDF</translate> <font-awesome-icon icon="times" class="ml-auto text-muted" @click="$store.commit('application/showPdfTool', false)" ></font-awesome-icon> </h6> <div class="p-3"> <b><translate>Choose format:</translate></b> <select v-model="form.format" class="form-control d-block w-100"> <option value="landscape"><translate>landscape</translate></option> <option value="portrait"><translate>portrait</translate></option> </select> <select v-model="form.resolution" class="form-control d-block w-100"> <option value="80">80 dpi</option> <option value="120">120 dpi</option> <option value="200">200 dpi</option> </select> <select v-model="form.paperSize" class="form-control d-block w-100"> <option value="a3"><translate>ISO A3</translate></option> <option value="a4"><translate>ISO A4</translate></option> </select> <small class="d-block my-2"> <input type="radio" id="pdfexport-downloadtype-download" value="download" v-model="form.downloadType" selected /> <label for="pdfexport-downloadtype-download" class="ml-1 mr-2"> <translate>Download</translate> </label> <input type="radio" id="pdfexport-downloadtype-open" value="open" v-model="form.downloadType" /> <label for="pdfexport-downloadtype-open" class="ml-1"> <translate>Open in new window</translate> </label> </small> <button @click="download" type="button" class="btn btn-sm btn-info d-block w-100" > <translate>Generate PDF</translate> </button> </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, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * * Markus Kottländer <markus.kottlaender@intevation.de> * * Bernhard E. Reiter <bernhard@intevation.de> * * Fadi Abbud <fadi.abbud@intevation.de> */ import { mapState } from "vuex"; import jsPDF from "jspdf"; import { getPointResolution } from "ol/proj.js"; var paperSizes = { // in millimeter, landscape [width, height] a3: [420, 297], a4: [297, 210] }; export default { name: "pdftool", data() { return { form: { format: "landscape", paperSize: "a4", downloadType: "download", resolution: "120" } }; }, computed: { ...mapState("map", ["openLayersMap"]), ...mapState("application", ["showPdfTool"]), ...mapState("bottlenecks", ["selectedSurvey"]) }, methods: { download() { // FUTURE: disable button while working on it console.log( "will generate pdf with", this.form.paperSize, 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]; } else { // switch width and height width = paperSizes[this.form.paperSize][1]; height = paperSizes[this.form.paperSize][0]; } // FUTURE: consider margins console.log(width, height); // dots per mm = dots per inch / (25.4 mm/inch) var pixelsPerMapMillimeter = this.form.resolution / 25.4; var mapSizeForPrint = [ // in pixel Math.round(width * pixelsPerMapMillimeter), Math.round(height * pixelsPerMapMillimeter) ]; // generate PDF and open it // our units are milimeters; width 0 x height 0 is left upper corner // Step 1 prepare and save current map extend // Then add callback "rendercomplete" for Step 3 // which will generate the pdf and resets the map view // Step 2 which starts rendering a map with the necessary image size var map = this.openLayersMap; var mapSize = map.getSize(); // size in pixels of the map in the DOM // Calculate the extent for the current view state and the passed size. // The size is the pixel dimensions of the box into which the calculated // extent should fit. var mapExtent = map.getView().calculateExtent(mapSize); var pdf = new jsPDF(this.form.format, "mm", this.form.paperSize); var scalebarSize = this.form.format === "portrait" && this.form.paperSize === "a4" ? 10 : 15; var northarrowSize = 3; var self = this; // set a callback for after the next complete rendering of the map map.once("rendercomplete", function(event) { let canvas = event.context.canvas; // because we are using Web Mercator, a pixel represents // a differently sizes spot depending on the place of the map. // So we use a value calculated from the center of the current view. let view = map.getView(); let proj = view.getProjection(); let metersPerPixel = // average meters (reality) per pixel (map) getPointResolution(proj, view.getResolution(), view.getCenter()) * proj.getMetersPerUnit(); console.log("metersPerPixel = ", metersPerPixel); var data = canvas.toDataURL("image/jpeg"); pdf.addImage(data, "JPEG", 0, 0, width, height); self.addRoundedBox( pdf, width - scalebarSize * 5.5, height - scalebarSize, scalebarSize * 5, scalebarSize ); self.addScalebar( pdf, width - scalebarSize * 5, height - scalebarSize / 2, scalebarSize, scalebarSize * pixelsPerMapMillimeter * metersPerPixel ); self.addText( pdf, width - scalebarSize * 5, height - scalebarSize * 0.6, 10, "black", 50, "Scale 1:" + Math.round(1000 * pixelsPerMapMillimeter * metersPerPixel) ); //self.addText(pdf, 150, 20, 10, "black", 70, "some text"); self.addNorthArrow(pdf, 15, 8, northarrowSize); pdf.save("map.pdf"); // reset to original size map.setSize(mapSize); map.getView().fit(mapExtent, { size: mapSize }); // FUTURE: re-enable button when done }); // trigger rendering map.setSize(mapSizeForPrint); map.getView().fit(mapExtent, { size: mapSizeForPrint }); // TODO: replace this src with an API reponse after actually generating PDFs /* let src = this.form.format === "landscape" ? "/img/PrintTemplate-Var2-Landscape.pdf" : "/img/PrintTemplate-Var2-Portrait.pdf"; let a = document.createElement("a"); a.href = src; 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); */ }, addRoundedBox(doc, 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"); }, addScalebar(doc, x, y, size, realLength) { // realLength as number in meters (reality) 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"); doc.text(x + size, y + 3, Math.round(realLength).toString()); doc.text(x + size * 2, y + 3, Math.round(realLength * 2).toString()); doc.text( x + size * 4, y + 3, Math.round(realLength * 4).toString() + " m" ); }, addNorthArrow(doc, x1, y1, 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"); }, // 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); // get the longest line to fit the white backround to it var longestString = ""; textLines.forEach(function(element) { if (element.length > longestString.length) longestString = element; }); var indexOfMaxString = textLines.indexOf(longestString); // white background (rectangular) around the text doc.setFillColor(255, 255, 255); doc.setDrawColor(255, 255, 255); doc.rect( postitionX - doc.getStringUnitWidth(textLines[indexOfMaxString]) / size, size > 10 ? positionY - size / 1.8 : positionY - size / 2.4, doc.getStringUnitWidth(textLines[indexOfMaxString]) * (size / 2.6), textLines.length * (size / 2), "FD" ); //rounded rectangular /* doc.roundedRect( postitionX - doc.getStringUnitWidth(textLines[indexOfMaxString]) / size, size > 10 ? positionY - size / 1.8 : positionY - size / 2.6, doc.getStringUnitWidth(textLines[indexOfMaxString]) * (size / 2.6), textLines.length * (size / 2), 3, 3, "FD" ); */ doc.setTextColor(color); doc.setFontSize(size); doc.text(postitionX, positionY, textLines); } } }; </script>