mapcomplete/UI/ExportPDF.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

197 lines
6.6 KiB
TypeScript
Raw Normal View History

2022-09-08 21:40:48 +02:00
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"
2021-11-07 16:34:51 +01:00
/**
* 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
*/
2021-07-28 02:51:07 +02:00
export default class ExportPDF {
// dimensions of the map in milimeter
public isRunning = new UIEventSource(true)
2021-07-28 02:51:07 +02:00
// A4: 297 * 210mm
2022-09-08 21:40:48 +02:00
private readonly mapW = 297
private readonly mapH = 210
2021-07-28 02:51:07 +02:00
private readonly scaling = 2
2022-09-08 21:40:48 +02:00
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
2021-07-28 02:51:07 +02:00
// We create a minimap at the given location and attach it to the given 'hidden' element
2022-09-08 21:40:48 +02:00
const l = options.location.data
2021-07-28 12:36:39 +02:00
const loc = {
2021-07-28 15:14:13 +02:00
lat: l.lat,
2021-07-28 12:36:39 +02:00
lon: l.lon,
2022-09-08 21:40:48 +02:00
zoom: l.zoom + 1,
2021-07-28 12:36:39 +02:00
}
2021-07-28 15:14:13 +02:00
const minimap = Minimap.createMiniMap({
2021-07-28 12:36:39 +02:00
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
2021-07-28 02:51:07 +02:00
background: options.background,
2021-07-28 12:36:39 +02:00
allowMoving: false,
2022-09-08 21:40:48 +02:00
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),
2021-07-28 02:51:07 +02:00
})
2022-09-08 21:40:48 +02:00
minimap.SetStyle(
`width: ${this.mapW * this.scaling}mm; height: ${this.mapH * this.scaling}mm;`
)
2021-07-28 02:51:07 +02:00
minimap.AttachTo(options.freeDivId)
// Next: we prepare the features. Only fully contained features are shown
2022-09-08 21:40:48 +02:00
minimap.leafletMap.addCallbackAndRunD((leaflet) => {
2021-07-28 02:51:07 +02:00
const bounds = BBox.fromLeafletBounds(leaflet.getBounds().pad(0.2))
2022-09-08 21:40:48 +02:00
options.features.GetTilesPerLayerWithin(bounds, (tile) => {
2021-11-07 16:34:51 +01:00
if (tile.layer.layerDef.minzoom > l.zoom) {
return
}
2022-09-08 21:40:48 +02:00
if (tile.layer.layerDef.id.startsWith("note_import")) {
2022-01-31 11:01:05 +01:00
// Don't export notes to import
2022-09-08 21:40:48 +02:00
return
2022-01-31 11:01:05 +01:00
}
2022-09-08 21:40:48 +02:00
new ShowDataLayer({
features: tile,
leafletMap: minimap.leafletMap,
layerToShow: tile.layer.layerDef,
doShowLayer: tile.layer.isDisplayed,
state: undefined,
})
})
})
2021-07-28 02:51:07 +02:00
State.state.AddAllOverlaysToMap(minimap.leafletMap)
2021-07-28 02:51:07 +02:00
}
2021-07-28 15:14:13 +02:00
private cleanup() {
2021-09-09 23:24:21 +02:00
new FixedUiElement("Screenshot taken!").AttachTo(this.freeDivId)
2022-09-08 21:40:48 +02:00
this._screenhotTaken = true
2021-07-28 02:51:07 +02:00
}
private async CreatePdf(minimap: MinimapObj) {
2021-10-03 01:38:57 +02:00
console.log("PDF creation started")
2022-09-08 21:40:48 +02:00
const t = Translations.t.general.pdf
2021-10-03 01:38:57 +02:00
const layout = this._layout
2021-07-28 02:51:07 +02:00
2022-09-08 21:40:48 +02:00
let doc = new jsPDF("landscape")
2021-07-28 02:51:07 +02:00
const image = await minimap.TakeScreenshot()
2021-07-28 02:51:07 +02:00
// @ts-ignore
2022-09-08 21:40:48 +02:00
doc.addImage(image, "PNG", 0, 0, this.mapW, this.mapH)
2021-07-28 02:51:07 +02:00
doc.setDrawColor(255, 255, 255)
doc.setFillColor(255, 255, 255)
2022-09-08 21:40:48 +02:00
doc.roundedRect(12, 10, 145, 25, 5, 5, "FD")
2021-07-28 02:51:07 +02:00
doc.setFontSize(20)
doc.textWithLink(layout.title.txt, 40, 18.5, {
maxWidth: 125,
2022-09-08 21:40:48 +02:00
url: window.location.href,
2021-07-28 02:51:07 +02:00
})
doc.setFontSize(10)
doc.text(t.generatedWith.txt, 40, 23, {
2022-09-08 21:40:48 +02:00
maxWidth: 125,
2021-07-28 02:51:07 +02:00
})
const backgroundLayer: BaseLayer = State.state.backgroundLayer.data
2022-09-08 21:40:48 +02:00
const attribution = new FixedUiElement(
backgroundLayer.layer().getAttribution() ?? backgroundLayer.name
).ConstructElement().textContent
doc.textWithLink(t.attr.txt, 40, 26.5, {
maxWidth: 125,
2022-09-08 21:40:48 +02:00
url: "https://www.openstreetmap.org/copyright",
})
2022-09-08 21:40:48 +02:00
doc.text(
t.attrBackground.Subs({
background: attribution,
}).txt,
40,
30
)
let date = new Date().toISOString().substr(0, 16)
doc.setFontSize(7)
2022-09-08 21:40:48 +02:00
doc.text(
t.versionInfo.Subs({
version: Constants.vNumber,
date: date,
}).txt,
40,
34,
{
maxWidth: 125,
}
)
2021-07-28 02:51:07 +02:00
// Add the logo of the layout
2022-09-08 21:40:48 +02:00
let img = document.createElement("img")
2021-07-28 02:51:07 +02:00
const imgSource = layout.icon
2022-09-08 21:40:48 +02:00
const imgType = imgSource.substr(imgSource.lastIndexOf(".") + 1)
2021-07-28 02:51:07 +02:00
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
2022-09-08 21:40:48 +02:00
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
canvas.width = 500
canvas.height = 500
img.style.width = "100%"
img.style.height = "100%"
2022-09-08 21:40:48 +02:00
ctx.drawImage(img, 0, 0, 500, 500)
const base64img = canvas.toDataURL("image/png")
2022-09-08 21:40:48 +02:00
doc.addImage(base64img, "png", 15, 12, 20, 20)
} else {
try {
2022-09-08 21:40:48 +02:00
doc.addImage(img, imgType, 15, 12, 20, 20)
} catch (e) {
console.error(e)
}
2021-07-28 02:51:07 +02:00
}
2022-09-08 21:40:48 +02:00
doc.save(`MapComplete_${layout.title.txt}_${date}.pdf`)
2021-07-28 02:51:07 +02:00
this.isRunning.setData(false)
2021-07-28 02:51:07 +02:00
}
}