forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			196 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import jsPDF from "jspdf"
 | |
| import { UIEventSource } from "../Logic/UIEventSource"
 | |
| import Minimap, { MinimapObj } from "./Base/Minimap"
 | |
| import Loc from "../Models/Loc"
 | |
| import BaseLayer from "../Models/BaseLayer"
 | |
| import { FixedUiElement } from "./Base/FixedUiElement"
 | |
| import Translations from "./i18n/Translations"
 | |
| import State from "../State"
 | |
| import Constants from "../Models/Constants"
 | |
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | |
| import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"
 | |
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"
 | |
| import { BBox } from "../Logic/BBox"
 | |
| 
 | |
| /**
 | |
|  * Creates screenshoter to take png screenshot
 | |
|  * Creates jspdf and downloads it
 | |
|  *        -    landscape pdf
 | |
|  *
 | |
|  * To add new layout:
 | |
|  *        -    add new possible layout name in constructor
 | |
|  *        -    add new layout in "PDFLayout"
 | |
|  *                -> in there are more instructions
 | |
|  */
 | |
| export default class ExportPDF {
 | |
|     // dimensions of the map in milimeter
 | |
|     public isRunning = new UIEventSource(true)
 | |
|     // A4: 297 * 210mm
 | |
|     private readonly mapW = 297
 | |
|     private readonly mapH = 210
 | |
|     private readonly scaling = 2
 | |
|     private readonly freeDivId: string
 | |
|     private readonly _layout: LayoutConfig
 | |
|     private _screenhotTaken = false
 | |
| 
 | |
|     constructor(options: {
 | |
|         freeDivId: string
 | |
|         location: UIEventSource<Loc>
 | |
|         background?: UIEventSource<BaseLayer>
 | |
|         features: FeaturePipeline
 | |
|         layout: LayoutConfig
 | |
|     }) {
 | |
|         this.freeDivId = options.freeDivId
 | |
|         this._layout = options.layout
 | |
|         const self = this
 | |
| 
 | |
|         // We create a minimap at the given location and attach it to the given 'hidden' element
 | |
| 
 | |
|         const l = options.location.data
 | |
|         const loc = {
 | |
|             lat: l.lat,
 | |
|             lon: l.lon,
 | |
|             zoom: l.zoom + 1,
 | |
|         }
 | |
| 
 | |
|         const minimap = Minimap.createMiniMap({
 | |
|             location: new UIEventSource<Loc>(loc), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot
 | |
|             background: options.background,
 | |
|             allowMoving: false,
 | |
|             onFullyLoaded: (_) =>
 | |
|                 window.setTimeout(() => {
 | |
|                     if (self._screenhotTaken) {
 | |
|                         return
 | |
|                     }
 | |
|                     try {
 | |
|                         self.CreatePdf(minimap)
 | |
|                             .then(() => self.cleanup())
 | |
|                             .catch(() => self.cleanup())
 | |
|                     } catch (e) {
 | |
|                         console.error(e)
 | |
|                         self.cleanup()
 | |
|                     }
 | |
|                 }, 500),
 | |
|         })
 | |
| 
 | |
|         minimap.SetStyle(
 | |
|             `width: ${this.mapW * this.scaling}mm; height: ${this.mapH * this.scaling}mm;`
 | |
|         )
 | |
|         minimap.AttachTo(options.freeDivId)
 | |
| 
 | |
|         // Next: we prepare the features. Only fully contained features are shown
 | |
|         minimap.leafletMap.addCallbackAndRunD((leaflet) => {
 | |
|             const bounds = BBox.fromLeafletBounds(leaflet.getBounds().pad(0.2))
 | |
|             options.features.GetTilesPerLayerWithin(bounds, (tile) => {
 | |
|                 if (tile.layer.layerDef.minzoom > l.zoom) {
 | |
|                     return
 | |
|                 }
 | |
|                 if (tile.layer.layerDef.id.startsWith("note_import")) {
 | |
|                     // Don't export notes to import
 | |
|                     return
 | |
|                 }
 | |
|                 new ShowDataLayer({
 | |
|                     features: tile,
 | |
|                     leafletMap: minimap.leafletMap,
 | |
|                     layerToShow: tile.layer.layerDef,
 | |
|                     doShowLayer: tile.layer.isDisplayed,
 | |
|                     state: undefined,
 | |
|                 })
 | |
|             })
 | |
|         })
 | |
| 
 | |
|         State.state.AddAllOverlaysToMap(minimap.leafletMap)
 | |
|     }
 | |
| 
 | |
|     private cleanup() {
 | |
|         new FixedUiElement("Screenshot taken!").AttachTo(this.freeDivId)
 | |
|         this._screenhotTaken = true
 | |
|     }
 | |
| 
 | |
|     private async CreatePdf(minimap: MinimapObj) {
 | |
|         console.log("PDF creation started")
 | |
|         const t = Translations.t.general.pdf
 | |
|         const layout = this._layout
 | |
| 
 | |
|         let doc = new jsPDF("landscape")
 | |
| 
 | |
|         const image = await minimap.TakeScreenshot()
 | |
|         // @ts-ignore
 | |
|         doc.addImage(image, "PNG", 0, 0, this.mapW, this.mapH)
 | |
| 
 | |
|         doc.setDrawColor(255, 255, 255)
 | |
|         doc.setFillColor(255, 255, 255)
 | |
|         doc.roundedRect(12, 10, 145, 25, 5, 5, "FD")
 | |
| 
 | |
|         doc.setFontSize(20)
 | |
|         doc.textWithLink(layout.title.txt, 40, 18.5, {
 | |
|             maxWidth: 125,
 | |
|             url: window.location.href,
 | |
|         })
 | |
|         doc.setFontSize(10)
 | |
|         doc.text(t.generatedWith.txt, 40, 23, {
 | |
|             maxWidth: 125,
 | |
|         })
 | |
|         const backgroundLayer: BaseLayer = State.state.backgroundLayer.data
 | |
|         const attribution = new FixedUiElement(
 | |
|             backgroundLayer.layer().getAttribution() ?? backgroundLayer.name
 | |
|         ).ConstructElement().textContent
 | |
|         doc.textWithLink(t.attr.txt, 40, 26.5, {
 | |
|             maxWidth: 125,
 | |
|             url: "https://www.openstreetmap.org/copyright",
 | |
|         })
 | |
| 
 | |
|         doc.text(
 | |
|             t.attrBackground.Subs({
 | |
|                 background: attribution,
 | |
|             }).txt,
 | |
|             40,
 | |
|             30
 | |
|         )
 | |
| 
 | |
|         let date = new Date().toISOString().substr(0, 16)
 | |
| 
 | |
|         doc.setFontSize(7)
 | |
|         doc.text(
 | |
|             t.versionInfo.Subs({
 | |
|                 version: Constants.vNumber,
 | |
|                 date: date,
 | |
|             }).txt,
 | |
|             40,
 | |
|             34,
 | |
|             {
 | |
|                 maxWidth: 125,
 | |
|             }
 | |
|         )
 | |
| 
 | |
|         // Add the logo of the layout
 | |
|         let img = document.createElement("img")
 | |
|         const imgSource = layout.icon
 | |
|         const imgType = imgSource.substring(imgSource.lastIndexOf(".") + 1)
 | |
|         img.src = imgSource
 | |
|         if (imgType.toLowerCase() === "svg") {
 | |
|             new FixedUiElement("").AttachTo(this.freeDivId)
 | |
| 
 | |
|             // This is an svg image, we use the canvas to convert it to a png
 | |
|             const canvas = document.createElement("canvas")
 | |
|             const ctx = canvas.getContext("2d")
 | |
|             canvas.width = 500
 | |
|             canvas.height = 500
 | |
|             img.style.width = "100%"
 | |
|             img.style.height = "100%"
 | |
|             ctx.drawImage(img, 0, 0, 500, 500)
 | |
|             const base64img = canvas.toDataURL("image/png")
 | |
|             doc.addImage(base64img, "png", 15, 12, 20, 20)
 | |
|         } else {
 | |
|             try {
 | |
|                 doc.addImage(img, imgType, 15, 12, 20, 20)
 | |
|             } catch (e) {
 | |
|                 console.error(e)
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         doc.save(`MapComplete_${layout.title.txt}_${date}.pdf`)
 | |
| 
 | |
|         this.isRunning.setData(false)
 | |
|     }
 | |
| }
 |