| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import jsPDF, { Matrix } from "jspdf" | 
					
						
							|  |  |  | import { Translation, TypedTranslation } from "../UI/i18n/Translation" | 
					
						
							|  |  |  | import { PngMapCreator } from "./pngMapCreator" | 
					
						
							|  |  |  | import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  | import "../assets/fonts/Ubuntu-M-normal.js" | 
					
						
							|  |  |  | import "../assets/fonts/Ubuntu-L-normal.js" | 
					
						
							|  |  |  | import "../assets/fonts/UbuntuMono-B-bold.js" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { makeAbsolute, parseSVG } from "svg-path-parser" | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  | import Translations from "../UI/i18n/Translations" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { Utils } from "../Utils" | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  | import Constants from "../Models/Constants" | 
					
						
							| 
									
										
										
										
											2023-04-27 16:32:36 +02:00
										 |  |  | import ThemeViewState from "../Models/ThemeViewState" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { Store, UIEventSource } from "../Logic/UIEventSource" | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class SvgToPdfInternals { | 
					
						
							|  |  |  |     private static readonly dummyDoc: jsPDF = new jsPDF() | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     private readonly doc: jsPDF | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     private readonly matrices: Matrix[] = [] | 
					
						
							|  |  |  |     private readonly matricesInverted: Matrix[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     private currentMatrix: Matrix | 
					
						
							|  |  |  |     private currentMatrixInverted: Matrix | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     private readonly extractTranslation: (string) => string | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     private readonly page: SvgToPdfPage | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |     private readonly usedRectangles = new Set<string>() | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     constructor(advancedApi: jsPDF, page: SvgToPdfPage, extractTranslation: (string) => string) { | 
					
						
							|  |  |  |         this.page = page | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         this.doc = advancedApi | 
					
						
							|  |  |  |         this.extractTranslation = (s) => extractTranslation(s)?.replace(/ /g, " ") | 
					
						
							|  |  |  |         this.currentMatrix = this.doc.unitMatrix | 
					
						
							|  |  |  |         this.currentMatrixInverted = this.doc.unitMatrix | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static extractMatrix(element: Element): Matrix { | 
					
						
							|  |  |  |         const t = element.getAttribute("transform") | 
					
						
							|  |  |  |         if (t === null) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             return null | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-22 00:35:37 +02:00
										 |  |  |         const scaleMatch = t.match(/scale\(([-0-9.]+)\)/) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         if (scaleMatch !== null) { | 
					
						
							|  |  |  |             const s = Number(scaleMatch[1]) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             return SvgToPdfInternals.dummyDoc.Matrix(1 / s, 0, 0, 1 / s, 0, 0) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-22 00:35:37 +02:00
										 |  |  |         const translateMatch = t.match(/translate\(([-0-9.]+), ?([-0-9.]*)\)/) | 
					
						
							|  |  |  |         if (translateMatch !== null) { | 
					
						
							|  |  |  |             const dx = Number(translateMatch[1]) | 
					
						
							|  |  |  |             const dy = Number(translateMatch[2]) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             return SvgToPdfInternals.dummyDoc.Matrix(1, 0, 0, 1, dx, dy) | 
					
						
							| 
									
										
										
										
											2022-09-22 00:35:37 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const transformMatch = t.match( | 
					
						
							|  |  |  |             /matrix\(([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*)\)/ | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         if (transformMatch !== null) { | 
					
						
							|  |  |  |             const vals = [1, 0, 0, 1, 0, 0] | 
					
						
							|  |  |  |             const invVals = [1, 0, 0, 1, 0, 0] | 
					
						
							|  |  |  |             for (let i = 0; i < 6; i++) { | 
					
						
							|  |  |  |                 const ti = Number(transformMatch[i + 1]) | 
					
						
							|  |  |  |                 if (ti == 0) { | 
					
						
							|  |  |  |                     vals[i] = 0 | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     invVals[i] = 1 / ti | 
					
						
							|  |  |  |                     vals[i] = ti | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             return SvgToPdfInternals.dummyDoc.Matrix( | 
					
						
							|  |  |  |                 vals[0], | 
					
						
							|  |  |  |                 vals[1], | 
					
						
							|  |  |  |                 vals[2], | 
					
						
							|  |  |  |                 vals[3], | 
					
						
							|  |  |  |                 vals[4], | 
					
						
							|  |  |  |                 vals[5] | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         return null | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static parseCss(styleContent: string, separator: string = ";"): Record<string, string> { | 
					
						
							|  |  |  |         if (styleContent === undefined || styleContent === null) { | 
					
						
							|  |  |  |             return {} | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const r: Record<string, string> = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const rule of styleContent.split(separator)) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             const [k, v] = rule.split(":").map((x) => x.trim()) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             r[k] = v | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return r | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     static attrNumber(element: Element, name: string, recurseup: boolean = true): number { | 
					
						
							|  |  |  |         const a = SvgToPdfInternals.attr(element, name, recurseup) | 
					
						
							|  |  |  |         const n = parseFloat(a) | 
					
						
							|  |  |  |         if (!isNaN(n)) { | 
					
						
							|  |  |  |             return n | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         return undefined | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Helper function to calculate where the given point will end up. | 
					
						
							|  |  |  |      * ALl the transforms of the parent elements are taking into account | 
					
						
							|  |  |  |      * @param mapSpec | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static GetActualXY(mapSpec: SVGTSpanElement): { x: number; y: number } { | 
					
						
							|  |  |  |         let runningM = SvgToPdfInternals.dummyDoc.unitMatrix | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let e: Element = mapSpec | 
					
						
							|  |  |  |         do { | 
					
						
							|  |  |  |             const m = SvgToPdfInternals.extractMatrix(e) | 
					
						
							|  |  |  |             if (m !== null) { | 
					
						
							|  |  |  |                 runningM = SvgToPdfInternals.dummyDoc.matrixMult(runningM, m) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             e = e.parentElement | 
					
						
							|  |  |  |         } while (e !== null && e.parentElement != e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const x = SvgToPdfInternals.attrNumber(mapSpec, "x") | 
					
						
							|  |  |  |         const y = SvgToPdfInternals.attrNumber(mapSpec, "y") | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         return runningM.applyToPoint({ x, y }) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     private static attr( | 
					
						
							|  |  |  |         element: Element, | 
					
						
							|  |  |  |         name: string, | 
					
						
							|  |  |  |         recurseup: boolean = true | 
					
						
							|  |  |  |     ): string | undefined { | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         if (element === null || element === undefined) { | 
					
						
							|  |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const a = element.getAttribute(name) | 
					
						
							|  |  |  |         if (a !== null && a !== undefined) { | 
					
						
							|  |  |  |             return a | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (recurseup && element.parentElement !== undefined && element.parentElement !== element) { | 
					
						
							|  |  |  |             return SvgToPdfInternals.attr(element.parentElement, name, recurseup) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return undefined | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Reads the 'style'-element recursively | 
					
						
							|  |  |  |      * @param element | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static css(element: Element): Record<string, string> { | 
					
						
							|  |  |  |         if (element.parentElement == undefined || element.parentElement == element) { | 
					
						
							|  |  |  |             return SvgToPdfInternals.parseCss(element.getAttribute("style")) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const css = SvgToPdfInternals.css(element.parentElement) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         const style = element.getAttribute("style") | 
					
						
							|  |  |  |         if (style === undefined || style == null) { | 
					
						
							|  |  |  |             return css | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (const rule of style.split(";")) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             const [k, v] = rule.split(":").map((x) => x.trim()) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             css[k] = v | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return css | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     applyMatrices(): void { | 
					
						
							|  |  |  |         let multiplied = this.doc.unitMatrix | 
					
						
							|  |  |  |         let multipliedInv = this.doc.unitMatrix | 
					
						
							|  |  |  |         for (const matrix of this.matrices) { | 
					
						
							|  |  |  |             multiplied = this.doc.matrixMult(multiplied, matrix) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         for (const matrix of this.matricesInverted) { | 
					
						
							|  |  |  |             multipliedInv = this.doc.matrixMult(multiplied, matrix) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.currentMatrix = multiplied | 
					
						
							|  |  |  |         this.currentMatrixInverted = multipliedInv | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     addMatrix(m: Matrix) { | 
					
						
							|  |  |  |         this.matrices.push(m) | 
					
						
							|  |  |  |         this.matricesInverted.push(m.inversed()) | 
					
						
							|  |  |  |         this.doc.setCurrentTransformationMatrix(m) | 
					
						
							|  |  |  |         this.applyMatrices() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public setTransform(element: Element): boolean { | 
					
						
							|  |  |  |         const m = SvgToPdfInternals.extractMatrix(element) | 
					
						
							|  |  |  |         if (m === null) { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.addMatrix(m) | 
					
						
							|  |  |  |         return true | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public undoTransform(): void { | 
					
						
							|  |  |  |         this.matrices.pop() | 
					
						
							|  |  |  |         const i = this.matricesInverted.pop() | 
					
						
							|  |  |  |         this.doc.setCurrentTransformationMatrix(i) | 
					
						
							|  |  |  |         this.applyMatrices() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public handleElement(element: SVGSVGElement | Element): void { | 
					
						
							|  |  |  |         const isTransformed = this.setTransform(element) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         this.page.status.set("Handling element " + element.tagName + " " + element.id) | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         try { | 
					
						
							|  |  |  |             if (element.tagName === "tspan") { | 
					
						
							|  |  |  |                 if (element.childElementCount == 0) { | 
					
						
							|  |  |  |                     this.drawTspan(element) | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     for (let child of Array.from(element.children)) { | 
					
						
							|  |  |  |                         this.handleElement(child) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (element.tagName === "image") { | 
					
						
							|  |  |  |                 this.drawImage(element) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (element.tagName === "path") { | 
					
						
							|  |  |  |                 this.drawPath(<any>element) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (element.tagName === "g" || element.tagName === "text") { | 
					
						
							|  |  |  |                 for (let child of Array.from(element.children)) { | 
					
						
							|  |  |  |                     this.handleElement(child) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (element.tagName === "rect") { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |                 if (!this.usedRectangles.has(element.id)) { | 
					
						
							|  |  |  |                     this.drawRect(<SVGRectElement>element) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (element.tagName === "circle") { | 
					
						
							|  |  |  |                 this.drawCircle(<any>element) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.error("Could not handle element", element, "due to", e) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (isTransformed) { | 
					
						
							|  |  |  |             this.undoTransform() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private drawRect(element: SVGRectElement) { | 
					
						
							|  |  |  |         const x = Number(element.getAttribute("x")) | 
					
						
							|  |  |  |         const y = Number(element.getAttribute("y")) | 
					
						
							|  |  |  |         const width = Number(element.getAttribute("width")) | 
					
						
							|  |  |  |         const height = Number(element.getAttribute("height")) | 
					
						
							|  |  |  |         const ry = SvgToPdfInternals.attrNumber(element, "ry", false) ?? 0 | 
					
						
							|  |  |  |         const rx = SvgToPdfInternals.attrNumber(element, "rx", false) ?? 0 | 
					
						
							|  |  |  |         const css = SvgToPdfInternals.css(element) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         this.doc.saveGraphicsState() | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             let color = css["fill"] ?? "black" | 
					
						
							|  |  |  |             let opacity = 1 | 
					
						
							|  |  |  |             if (css["fill-opacity"]) { | 
					
						
							|  |  |  |                 opacity = Number(css["fill-opacity"]) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 this.doc.setGState(this.doc.GState({ opacity: opacity })) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.doc.setFillColor(color) | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             this.doc.roundedRect(x, y, width, height, rx, ry, "F") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (css["stroke"] && css["stroke"] !== "none") { | 
					
						
							|  |  |  |             this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) | 
					
						
							|  |  |  |             this.doc.setDrawColor(css["stroke"] ?? "black") | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             if (css["opacity"]) { | 
					
						
							|  |  |  |                 const opacity = Number(css["opacity"]) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 this.doc.setGState(this.doc.GState({ "stroke-opacity": opacity })) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             this.doc.roundedRect(x, y, width, height, rx, ry, "S") | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         this.doc.restoreGraphicsState() | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private drawCircle(element: SVGCircleElement) { | 
					
						
							|  |  |  |         const x = Number(element.getAttribute("cx")) | 
					
						
							|  |  |  |         const y = Number(element.getAttribute("cy")) | 
					
						
							|  |  |  |         const r = Number(element.getAttribute("r")) | 
					
						
							|  |  |  |         const css = SvgToPdfInternals.css(element) | 
					
						
							|  |  |  |         if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { | 
					
						
							|  |  |  |             this.doc.setFillColor(css["fill"] ?? "black") | 
					
						
							|  |  |  |             this.doc.circle(x, y, r, "F") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (css["stroke"] && css["stroke"] !== "none") { | 
					
						
							|  |  |  |             this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) | 
					
						
							|  |  |  |             this.doc.setDrawColor(css["stroke"] ?? "black") | 
					
						
							|  |  |  |             this.doc.circle(x, y, r, "S") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private drawTspan(tspan: Element) { | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         const txt = tspan.textContent | 
					
						
							|  |  |  |         if (txt == "") { | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         let x = SvgToPdfInternals.attrNumber(tspan, "x") | 
					
						
							|  |  |  |         let y = SvgToPdfInternals.attrNumber(tspan, "y") | 
					
						
							|  |  |  |         const m = SvgToPdfInternals.extractMatrix(tspan.parentElement) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         const p = m?.inversed()?.applyToPoint({ x, y }) | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         x = p?.x ?? x | 
					
						
							|  |  |  |         y = p?.y ?? y | 
					
						
							|  |  |  |         const imageMatch = txt.match(/^\$img\(([^)]*)\)$/) | 
					
						
							|  |  |  |         if (imageMatch) { | 
					
						
							|  |  |  |             // We want to draw a special image
 | 
					
						
							|  |  |  |             const [_, key] = imageMatch | 
					
						
							|  |  |  |             console.log("Creating image with key", key, "searching rect in", x, y) | 
					
						
							|  |  |  |             const rectangle: SVGRectElement = this.page.findSmallestRectContaining(x, y, false) | 
					
						
							|  |  |  |             console.log("Got rect", rectangle) | 
					
						
							|  |  |  |             let w = SvgToPdfInternals.attrNumber(rectangle, "width") | 
					
						
							|  |  |  |             let h = SvgToPdfInternals.attrNumber(rectangle, "height") | 
					
						
							|  |  |  |             x = SvgToPdfInternals.attrNumber(rectangle, "x") | 
					
						
							|  |  |  |             y = SvgToPdfInternals.attrNumber(rectangle, "y") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Actually, dots per mm, not dots per inch ;)
 | 
					
						
							|  |  |  |             let dpi = 60 | 
					
						
							|  |  |  |             const img = this.page.options.createImage(key, dpi * w + "px", dpi * h + "px") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const canvas = document.createElement("canvas") | 
					
						
							|  |  |  |             const ctx = canvas.getContext("2d") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             canvas.width = w * dpi | 
					
						
							|  |  |  |             canvas.height = h * dpi | 
					
						
							|  |  |  |             img.style.width = `${w * dpi}px` | 
					
						
							|  |  |  |             img.style.height = `${h * dpi}px` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ctx.drawImage(img, 0, 0, w * dpi, h * dpi) | 
					
						
							|  |  |  |             const base64img = canvas.toDataURL("image/png") | 
					
						
							|  |  |  |             // Don't ask me why this magicFactor transformation is needed - but it works
 | 
					
						
							|  |  |  |             const magicFactor = 3.8 | 
					
						
							|  |  |  |             this.addMatrix(this.doc.Matrix(1 / magicFactor, 0, 0, 1 / magicFactor, 0, 0)) | 
					
						
							|  |  |  |             this.doc.addImage(base64img, "png", x, y, w, h) | 
					
						
							|  |  |  |             this.undoTransform() | 
					
						
							|  |  |  |             this.usedRectangles.add(rectangle.id) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 23:58:29 +02:00
										 |  |  |         let maxWidth: number = undefined | 
					
						
							|  |  |  |         let maxHeight: number = undefined | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         const css = SvgToPdfInternals.css(tspan) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         if (css["shape-inside"]) { | 
					
						
							|  |  |  |             const matched = css["shape-inside"].match(/url\(#([a-zA-Z0-9-]+)\)/) | 
					
						
							|  |  |  |             if (matched !== null) { | 
					
						
							|  |  |  |                 const rectId = matched[1] | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |                 const rect = this.page.rects[rectId]?.rect | 
					
						
							|  |  |  |                 if (rect) { | 
					
						
							|  |  |  |                     maxWidth = SvgToPdfInternals.attrNumber(rect, "width", false) | 
					
						
							|  |  |  |                     maxHeight = SvgToPdfInternals.attrNumber(rect, "height", false) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         let fontFamily = css["font-family"] ?? "Ubuntu" | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         if (fontFamily === "sans-serif") { | 
					
						
							|  |  |  |             fontFamily = "Ubuntu" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         let fontWeight = css["font-weight"] ?? "normal" | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         this.doc.setFont(fontFamily, fontWeight) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const fontColor = css["fill"] | 
					
						
							|  |  |  |         if (fontColor) { | 
					
						
							|  |  |  |             this.doc.setTextColor(fontColor) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             this.doc.setTextColor("black") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let fontsize = parseFloat(css["font-size"]) | 
					
						
							|  |  |  |         this.doc.setFontSize(fontsize * 2.5) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let textTemplate = tspan.textContent.split(" ") | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |         let result: string = "" | 
					
						
							|  |  |  |         let addSpace = false | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         for (let text of textTemplate) { | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |             if (text === "\\n") { | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                 result += "\n" | 
					
						
							|  |  |  |                 addSpace = false | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |             if (text === "\\n\\n") { | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                 result += "\n\n" | 
					
						
							|  |  |  |                 addSpace = false | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             if (text.startsWith(`$\{`)) { | 
					
						
							|  |  |  |                 if (addSpace) { | 
					
						
							|  |  |  |                     result += " " | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 result += this.extractTranslation(text) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             if (!text.startsWith("$")) { | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |                 if (addSpace) { | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                     result += " " | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 result += text | 
					
						
							|  |  |  |                 addSpace = true | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |             const list = text.match(/\$list\(([a-zA-Z0-9_.-]+)\)/) | 
					
						
							|  |  |  |             if (list) { | 
					
						
							|  |  |  |                 const key = list[1] | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                 let r = this.extractTranslation("$" + key + "0") | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 let i = 0 | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                 result += "\n" | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 while (r !== undefined && i < 100) { | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                     result += "• " + r + "\n" | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                     i++ | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                     r = this.extractTranslation("$" + key + i) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                 result += "\n" | 
					
						
							|  |  |  |                 addSpace = false | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 const found = this.extractTranslation(text) ?? text | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |                 if (addSpace) { | 
					
						
							| 
									
										
										
										
											2022-09-18 12:45:02 +02:00
										 |  |  |                     result += " " | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 result += found | 
					
						
							|  |  |  |                 addSpace = true | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         const options = {} | 
					
						
							|  |  |  |         if (maxWidth) { | 
					
						
							|  |  |  |             options["maxWidth"] = maxWidth | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         this.doc.text(result, x, y, options, this.currentMatrix) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private drawSvgViaCanvas(element: Element): void { | 
					
						
							|  |  |  |         const x = SvgToPdfInternals.attrNumber(element, "x") | 
					
						
							|  |  |  |         const y = SvgToPdfInternals.attrNumber(element, "y") | 
					
						
							|  |  |  |         const height = SvgToPdfInternals.attrNumber(element, "height") | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         const width = SvgToPdfInternals.attrNumber(element, "width") | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         const base64src = SvgToPdfInternals.attr(element, "xlink:href") | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const svgXml = atob(base64src.substring(base64src.indexOf(";base64,") + ";base64,".length)) | 
					
						
							|  |  |  |         const parser = new DOMParser() | 
					
						
							|  |  |  |         const xmlDoc = parser.parseFromString(svgXml, "text/xml") | 
					
						
							|  |  |  |         const svgRoot = xmlDoc.getElementsByTagName("svg")[0] | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         const svgWidth = SvgToPdfInternals.attrNumber(svgRoot, "width") | 
					
						
							|  |  |  |         const svgHeight = SvgToPdfInternals.attrNumber(svgRoot, "height") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         let img = this.page.images[base64src] | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         // 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 = svgWidth | 
					
						
							|  |  |  |         canvas.height = svgHeight | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         img.style.width = `${svgWidth}px` | 
					
						
							|  |  |  |         img.style.height = `${svgHeight}px` | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         ctx.drawImage(img, 0, 0, svgWidth, svgHeight) | 
					
						
							|  |  |  |         const base64img = canvas.toDataURL("image/png") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.addMatrix(this.doc.Matrix(width / svgWidth, 0, 0, height / svgHeight, 0, 0)) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         const p = this.currentMatrixInverted.applyToPoint({ x, y }) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         this.doc.addImage( | 
					
						
							|  |  |  |             base64img, | 
					
						
							|  |  |  |             "png", | 
					
						
							|  |  |  |             (p.x * svgWidth) / width, | 
					
						
							|  |  |  |             (p.y * svgHeight) / height, | 
					
						
							|  |  |  |             svgWidth, | 
					
						
							|  |  |  |             svgHeight | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         this.undoTransform() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private drawImage(element: Element): void { | 
					
						
							|  |  |  |         const href = SvgToPdfInternals.attr(element, "xlink:href") | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         if (href.endsWith("svg") || href.startsWith("data:image/svg")) { | 
					
						
							|  |  |  |             this.drawSvgViaCanvas(element) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             const x = SvgToPdfInternals.attrNumber(element, "x") | 
					
						
							|  |  |  |             const y = SvgToPdfInternals.attrNumber(element, "y") | 
					
						
							|  |  |  |             const width = SvgToPdfInternals.attrNumber(element, "width") | 
					
						
							|  |  |  |             const height = SvgToPdfInternals.attrNumber(element, "height") | 
					
						
							|  |  |  |             const base64src = SvgToPdfInternals.attr(element, "xlink:href") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.doc.addImage(base64src, x, y, width, height) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |     private drawPath(element: SVGPathElement): void { | 
					
						
							|  |  |  |         const path = element.getAttribute("d") | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const parsed: { code: string; x: number; y: number; x2?; y2?; x1?; y1? }[] = parseSVG(path) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         makeAbsolute(parsed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const c of parsed) { | 
					
						
							|  |  |  |             if (c.code === "C" || c.code === "c") { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 const command = { op: "c", c: [c.x1, c.y1, c.x2, c.y2, c.x, c.y] } | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |                 this.doc.path([command]) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |             if (c.code === "H") { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 const command = { op: "l", c: [c.x, c.y] } | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |                 this.doc.path([command]) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (c.code === "V") { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 const command = { op: "l", c: [c.x, c.y] } | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |                 this.doc.path([command]) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             this.doc.path([{ op: c.code.toLowerCase(), c: [c.x, c.y] }]) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         //"fill:#ffffff;stroke:#000000;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:20"
 | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const css = SvgToPdfInternals.css(element) | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |         if (css["color"] && css["color"].toLowerCase() !== "none") { | 
					
						
							|  |  |  |             this.doc.setDrawColor(css["color"]) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         if (css["stroke-width"]) { | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |             this.doc.setLineWidth(parseFloat(css["stroke-width"])) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (css["stroke-linejoin"] !== undefined) { | 
					
						
							|  |  |  |             this.doc.setLineJoin(css["stroke-linejoin"]) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |         let doFill = false | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         if (css["fill-rule"] === "evenodd") { | 
					
						
							|  |  |  |             this.doc.fillEvenOdd() | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |         } else if (css["fill"] && css["fill"] !== "none") { | 
					
						
							|  |  |  |             this.doc.setFillColor(css["fill"]) | 
					
						
							|  |  |  |             doFill = true | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (css["stroke"] && css["stroke"] !== "none") { | 
					
						
							|  |  |  |             this.doc.setDrawColor(css["stroke"]) | 
					
						
							|  |  |  |             if (doFill) { | 
					
						
							|  |  |  |                 this.doc.fillStroke() | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 this.doc.stroke() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else if (doFill) { | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |             this.doc.fill() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  | export interface SvgToPdfOptions { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     freeComponentId: string | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     disableMaps?: false | true | 
					
						
							|  |  |  |     textSubstitutions?: Record<string, string> | 
					
						
							|  |  |  |     beforePage?: (i: number) => void | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     overrideLocation?: { lat: number; lon: number } | 
					
						
							|  |  |  |     disableDataLoading?: boolean | false | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Override all the maps to generate with this map | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     state?: ThemeViewState | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     createImage(key: string, width: string, height: string): HTMLImageElement | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  | class SvgToPdfPage { | 
					
						
							|  |  |  |     public readonly _svgRoot: SVGSVGElement | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |     images: Record<string, HTMLImageElement> = {} | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     rects: Record<string, { rect: SVGRectElement; isInDef: boolean }> = {} | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |     readonly options: SvgToPdfOptions | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     private readonly importedTranslations: Record<string, string> = {} | 
					
						
							|  |  |  |     private readonly layerTranslations: Record<string, Record<string, any>> = {} | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Small indicator for humans | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private readonly _state: UIEventSource<string> | 
					
						
							|  |  |  |     private _isPrepared = false | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     public readonly status: UIEventSource<string> | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     constructor( | 
					
						
							|  |  |  |         page: string, | 
					
						
							|  |  |  |         state: UIEventSource<string>, | 
					
						
							|  |  |  |         options: SvgToPdfOptions, | 
					
						
							|  |  |  |         status: UIEventSource<string> | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         this._state = state | 
					
						
							| 
									
										
										
										
											2023-06-04 23:58:29 +02:00
										 |  |  |         this.options = options | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         this.status = status | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         const parser = new DOMParser() | 
					
						
							|  |  |  |         const xmlDoc = parser.parseFromString(page, "image/svg+xml") | 
					
						
							|  |  |  |         this._svgRoot = xmlDoc.getElementsByTagName("svg")[0] | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     private static blobToBase64(blob): Promise<string> { | 
					
						
							|  |  |  |         return new Promise((resolve, _) => { | 
					
						
							|  |  |  |             const reader = new FileReader() | 
					
						
							|  |  |  |             reader.onloadend = () => resolve(<string>reader.result) | 
					
						
							|  |  |  |             reader.readAsDataURL(blob) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |     public extractTranslations(): Set<string> { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const textContents: string[] = Array.from(this._svgRoot.getElementsByTagName("tspan")).map( | 
					
						
							|  |  |  |             (t) => t.textContent | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |         const translations = new Set<string>() | 
					
						
							|  |  |  |         for (const tc of textContents) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             const parts = tc.split(" ").filter((p) => p.startsWith("$") && p.indexOf("(") < 0) | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |             for (let part of parts) { | 
					
						
							|  |  |  |                 part = part.substring(1) // Drop the $
 | 
					
						
							|  |  |  |                 let path = part.split(".") | 
					
						
							|  |  |  |                 const importPath = this.importedTranslations[path[0]] | 
					
						
							|  |  |  |                 if (importPath) { | 
					
						
							|  |  |  |                     translations.add(importPath + "." + path.slice(1).join(".")) | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     translations.add(part) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return translations | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Does some preparatory work, most importantly gathering the map specifications into parameter `mapTextSpecs` and substituting translations | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     public async prepareElement( | 
					
						
							|  |  |  |         element: SVGSVGElement | Element, | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         mapTextSpecs: SVGTSpanElement[], | 
					
						
							|  |  |  |         inDefs: boolean | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         if (element.tagName === "rect") { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             this.rects[element.id] = { rect: <SVGRectElement>element, isInDef: inDefs } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (element.tagName === "image") { | 
					
						
							|  |  |  |             await this.loadImage(element) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (element.tagName === "tspan" && element.childElementCount == 0) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             const specialValues = element.textContent.split(" ").filter((t) => t.startsWith("$")) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             for (let specialValue of specialValues) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                 const importMatch = element.textContent.match( | 
					
						
							|  |  |  |                     /\$import ([a-zA-Z-_0-9.? ]+) as ([a-zA-Z0-9]+)/ | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |                 if (importMatch !== null) { | 
					
						
							|  |  |  |                     const [, pathRaw, as] = importMatch | 
					
						
							|  |  |  |                     this.importedTranslations[as] = pathRaw | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                 const setPropertyMatch = element.textContent.match( | 
					
						
							|  |  |  |                     /\$set\(([a-zA-Z-_0-9.?:]+),(.+)\)/ | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |                 if (setPropertyMatch) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                     this.options.textSubstitutions[setPropertyMatch[1].trim()] = | 
					
						
							|  |  |  |                         setPropertyMatch[2].trim() | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 if (element.textContent.startsWith("$map(")) { | 
					
						
							|  |  |  |                     mapTextSpecs.push(<any>element) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         if ( | 
					
						
							|  |  |  |             element.tagName === "g" || | 
					
						
							|  |  |  |             element.tagName === "text" || | 
					
						
							|  |  |  |             element.tagName === "tspan" || | 
					
						
							|  |  |  |             element.tagName === "defs" | 
					
						
							|  |  |  |         ) { | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             for (let child of Array.from(element.children)) { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |                 await this.prepareElement(child, mapTextSpecs, inDefs || element.tagName === "defs") | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     public async PrepareLanguage(language: string) { | 
					
						
							|  |  |  |         // Always fetch the remote data - it's cached anyway
 | 
					
						
							|  |  |  |         this.layerTranslations[language] = await Utils.downloadJsonCached( | 
					
						
							|  |  |  |             "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/langs/layers/" + | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 language + | 
					
						
							|  |  |  |                 ".json", | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             24 * 60 * 60 * 1000 | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         const shared_questions = await Utils.downloadJsonCached( | 
					
						
							|  |  |  |             "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/langs/shared-questions/" + | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 language + | 
					
						
							|  |  |  |                 ".json", | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             24 * 60 * 60 * 1000 | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         this.layerTranslations[language]["shared-questions"] = shared_questions["shared_questions"] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public async Prepare() { | 
					
						
							|  |  |  |         if (this._isPrepared) { | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this._isPrepared = true | 
					
						
							|  |  |  |         const mapSpecs: SVGTSpanElement[] = [] | 
					
						
							|  |  |  |         for (let child of Array.from(this._svgRoot.children)) { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             await this.prepareElement(<any>child, mapSpecs, child.tagName === "defs") | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const mapSpec of mapSpecs) { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             await this.prepareMap(mapSpec, !this.options?.disableDataLoading) | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public drawPage(advancedApi: jsPDF, i: number, language): void { | 
					
						
							|  |  |  |         if (!this._isPrepared) { | 
					
						
							|  |  |  |             throw "Run 'Prepare()' first!" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this.options.beforePage) { | 
					
						
							|  |  |  |             this.options.beforePage(i) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         const internal = new SvgToPdfInternals(advancedApi, this, (key) => | 
					
						
							|  |  |  |             self.extractTranslation(key, language) | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |         for (let child of Array.from(this._svgRoot.children)) { | 
					
						
							|  |  |  |             internal.handleElement(<any>child) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     extractTranslation(text: string, language: string, strict: boolean = false) { | 
					
						
							|  |  |  |         if (text === "$version") { | 
					
						
							|  |  |  |             return ( | 
					
						
							|  |  |  |                 new Date().toISOString().substring(0, "2022-01-02THH:MM".length) + | 
					
						
							|  |  |  |                 " - v" + | 
					
						
							|  |  |  |                 Constants.vNumber | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         if (text.startsWith("${") && text.endsWith("}")) { | 
					
						
							|  |  |  |             const key = text.substring(2, text.length - 1) | 
					
						
							|  |  |  |             return this.options.textSubstitutions[key] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         const pathPart = text.match(/\$(([_a-zA-Z0-9? ]+\.)+[_a-zA-Z0-9? ]+)(.*)/) | 
					
						
							|  |  |  |         if (pathPart === null) { | 
					
						
							|  |  |  |             return text | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let t: any = Translations.t | 
					
						
							|  |  |  |         const path = pathPart[1].split(".") | 
					
						
							|  |  |  |         if (this.importedTranslations[path[0]]) { | 
					
						
							|  |  |  |             path.splice(0, 1, ...this.importedTranslations[path[0]].split(".")) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const rest = pathPart[3] ?? "" | 
					
						
							|  |  |  |         if (path[0] === "layer") { | 
					
						
							|  |  |  |             t = this.layerTranslations[language] | 
					
						
							|  |  |  |             if (t === undefined) { | 
					
						
							|  |  |  |                 console.error("No layerTranslation available for language " + language) | 
					
						
							|  |  |  |                 return text | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             path.splice(0, 1) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (const crumb of path) { | 
					
						
							|  |  |  |             t = t[crumb] | 
					
						
							|  |  |  |             if (t === undefined) { | 
					
						
							|  |  |  |                 console.error("No value found to substitute " + text, "the path is", path) | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (typeof t === "string") { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             t = new TypedTranslation({ "*": t }) | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (t instanceof TypedTranslation) { | 
					
						
							|  |  |  |             if (strict && (t.translations[language] ?? t.translations["*"]) === undefined) { | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return t.Subs(this.options.textSubstitutions).textFor(language) + rest | 
					
						
							|  |  |  |         } else if (t instanceof Translation) { | 
					
						
							|  |  |  |             if (strict && (t.translations[language] ?? t.translations["*"]) === undefined) { | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return (<Translation>t).textFor(language) + rest | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             console.error("Could not get textFor from ", t, "for path", text) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |     public findSmallestRectContaining(x: number, y: number, shouldBeInDefinitionSection: boolean) { | 
					
						
							|  |  |  |         let smallestRect: SVGRectElement = undefined | 
					
						
							|  |  |  |         let smallestSurface: number = undefined | 
					
						
							|  |  |  |         // We iterate over all the rectangles and pick the smallest (by surface area) that contains the upper left point of the tspan
 | 
					
						
							|  |  |  |         for (const id in this.rects) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             const { rect, isInDef } = this.rects[id] | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             if (shouldBeInDefinitionSection !== isInDef) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const rx = SvgToPdfInternals.attrNumber(rect, "x") | 
					
						
							|  |  |  |             const ry = SvgToPdfInternals.attrNumber(rect, "y") | 
					
						
							|  |  |  |             const w = SvgToPdfInternals.attrNumber(rect, "width") | 
					
						
							|  |  |  |             const h = SvgToPdfInternals.attrNumber(rect, "height") | 
					
						
							|  |  |  |             const inBounds = rx <= x && x <= rx + w && ry <= y && y <= ry + h | 
					
						
							|  |  |  |             if (!inBounds) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const surface = w * h | 
					
						
							|  |  |  |             if (smallestSurface === undefined || smallestSurface > surface) { | 
					
						
							|  |  |  |                 smallestSurface = surface | 
					
						
							|  |  |  |                 smallestRect = rect | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return smallestRect | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |     private loadImage(element: Element | string): Promise<void> { | 
					
						
							|  |  |  |         const xlink = typeof element === "string" ? element : element.getAttribute("xlink:href") | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         let img = document.createElement("img") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (xlink.startsWith("data:image/svg+xml;")) { | 
					
						
							|  |  |  |             const base64src = xlink | 
					
						
							|  |  |  |             let svgXml = atob( | 
					
						
							|  |  |  |                 base64src.substring(base64src.indexOf(";base64,") + ";base64,".length) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             const parser = new DOMParser() | 
					
						
							|  |  |  |             const xmlDoc = parser.parseFromString(svgXml, "text/xml") | 
					
						
							|  |  |  |             const svgRoot = xmlDoc.getElementsByTagName("svg")[0] | 
					
						
							|  |  |  |             const svgWidthStr = svgRoot.getAttribute("width") | 
					
						
							|  |  |  |             const svgHeightStr = svgRoot.getAttribute("height") | 
					
						
							|  |  |  |             const svgWidth = parseFloat(svgWidthStr) | 
					
						
							|  |  |  |             const svgHeight = parseFloat(svgHeightStr) | 
					
						
							|  |  |  |             if (!svgWidthStr.endsWith("px")) { | 
					
						
							|  |  |  |                 svgRoot.setAttribute("width", svgWidth + "px") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (!svgHeightStr.endsWith("px")) { | 
					
						
							|  |  |  |                 svgRoot.setAttribute("height", svgHeight + "px") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             img.src = "data:image/svg+xml;base64," + btoa(svgRoot.outerHTML) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             img.src = xlink | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.images[xlink] = img | 
					
						
							|  |  |  |         return new Promise((resolve) => { | 
					
						
							|  |  |  |             img.onload = (_) => { | 
					
						
							|  |  |  |                 resolve() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Replaces a mapSpec with the appropriate map | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |     private async prepareMap(mapSpec: SVGTSpanElement, loadData: boolean): Promise<void> { | 
					
						
							|  |  |  |         if (this.options.disableMaps) { | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         // Upper left point of the tspan
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         const { x, y } = SvgToPdfInternals.GetActualXY(mapSpec) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         let textElement: Element = mapSpec | 
					
						
							|  |  |  |         // We recurse up to get the actual, full specification
 | 
					
						
							|  |  |  |         while (textElement.tagName !== "text") { | 
					
						
							|  |  |  |             textElement = textElement.parentElement | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const spec = textElement.textContent | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         const smallestRect = this.findSmallestRectContaining(x, y, false) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         if (smallestRect === undefined) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             throw ( | 
					
						
							|  |  |  |                 "No rectangle found around " + | 
					
						
							|  |  |  |                 spec + | 
					
						
							|  |  |  |                 ". Draw a rectangle around it, the map will be projected on that one" | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const svgImage = document.createElement("image") | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         svgImage.setAttribute("x", smallestRect.getAttribute("x")) | 
					
						
							|  |  |  |         svgImage.setAttribute("y", smallestRect.getAttribute("y")) | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |         // width and height are in mm
 | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         const width = SvgToPdfInternals.attrNumber(smallestRect, "width") | 
					
						
							|  |  |  |         const height = SvgToPdfInternals.attrNumber(smallestRect, "height") | 
					
						
							|  |  |  |         svgImage.setAttribute("width", "" + width) | 
					
						
							|  |  |  |         svgImage.setAttribute("height", "" + height) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         let png: Blob | 
					
						
							|  |  |  |         if (this.options.state !== undefined) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             png = await new PngMapCreator(this.options.state, { | 
					
						
							|  |  |  |                 width, | 
					
						
							|  |  |  |                 height, | 
					
						
							|  |  |  |             }).CreatePng(this.options.freeComponentId, this._state) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             const match = spec.match(/\$map\(([^)]*)\)$/) | 
					
						
							|  |  |  |             if (match === null) { | 
					
						
							|  |  |  |                 throw "Invalid mapspec:" + spec | 
					
						
							| 
									
										
										
										
											2022-09-14 14:43:14 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             const params = SvgToPdfInternals.parseCss(match[1], ",") | 
					
						
							|  |  |  |             let layout = AllKnownLayouts.allKnownLayouts.get(params["theme"]) | 
					
						
							|  |  |  |             if (layout === undefined) { | 
					
						
							|  |  |  |                 console.error("Could not show map with parameters", params) | 
					
						
							|  |  |  |                 throw ( | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                     "Theme not found:" + | 
					
						
							|  |  |  |                     params["theme"] + | 
					
						
							|  |  |  |                     ". Use theme: to define which theme to use. " | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             layout.widenFactor = 0 | 
					
						
							|  |  |  |             layout.overpassTimeout = 600 | 
					
						
							|  |  |  |             layout.defaultBackgroundId = params["background"] ?? layout.defaultBackgroundId | 
					
						
							|  |  |  |             for (const paramsKey in params) { | 
					
						
							|  |  |  |                 if (paramsKey.startsWith("layer-")) { | 
					
						
							|  |  |  |                     const layerName = paramsKey.substring("layer-".length) | 
					
						
							|  |  |  |                     const key = params[paramsKey].toLowerCase().trim() | 
					
						
							|  |  |  |                     const layer = layout.layers.find((l) => l.id === layerName) | 
					
						
							|  |  |  |                     if (layer === undefined) { | 
					
						
							|  |  |  |                         throw "No layer found for " + paramsKey | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     if (key === "force") { | 
					
						
							|  |  |  |                         layer.minzoom = 0 | 
					
						
							|  |  |  |                         layer.minzoomVisible = 0 | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const zoom = Number(params["zoom"] ?? params["z"] ?? 14) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const state = new ThemeViewState(layout) | 
					
						
							|  |  |  |             state.mapProperties.location.setData({ | 
					
						
							|  |  |  |                 lat: this.options?.overrideLocation?.lat ?? Number(params["lat"] ?? 51.05016), | 
					
						
							|  |  |  |                 lon: this.options?.overrideLocation?.lon ?? Number(params["lon"] ?? 3.717842), | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             state.mapProperties.zoom.setData(zoom) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const fl = Array.from(state.layerState.filteredLayers.values()) | 
					
						
							|  |  |  |             for (const filteredLayer of fl) { | 
					
						
							|  |  |  |                 if (params["layer-" + filteredLayer.layerDef.id] !== undefined) { | 
					
						
							|  |  |  |                     filteredLayer.isDisplayed.setData( | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                         loadData && | 
					
						
							|  |  |  |                             params["layer-" + filteredLayer.layerDef.id].trim().toLowerCase() !== | 
					
						
							|  |  |  |                                 "false" | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |                 } else if (params["layers"] === "none") { | 
					
						
							|  |  |  |                     filteredLayer.isDisplayed.setData(false) | 
					
						
							|  |  |  |                 } else if (filteredLayer.layerDef.id.startsWith("note_import")) { | 
					
						
							|  |  |  |                     filteredLayer.isDisplayed.setData(false) | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (const paramsKey in params) { | 
					
						
							|  |  |  |                 if (paramsKey.startsWith("layer-")) { | 
					
						
							|  |  |  |                     const layerName = paramsKey.substring("layer-".length) | 
					
						
							|  |  |  |                     const key = params[paramsKey].toLowerCase().trim() | 
					
						
							|  |  |  |                     const isDisplayed = loadData && (key === "true" || key === "force") | 
					
						
							|  |  |  |                     const layer = fl.find((l) => l.layerDef.id === layerName) | 
					
						
							|  |  |  |                     if (!loadData) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                         console.log( | 
					
						
							|  |  |  |                             "Not loading map data as 'loadData' is falsed, this is probably a test run" | 
					
						
							|  |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |                     } else { | 
					
						
							|  |  |  |                         console.log( | 
					
						
							|  |  |  |                             "Setting ", | 
					
						
							|  |  |  |                             layer?.layerDef?.id, | 
					
						
							|  |  |  |                             " to visibility", | 
					
						
							|  |  |  |                             isDisplayed, | 
					
						
							|  |  |  |                             "(minzoom:", | 
					
						
							|  |  |  |                             layer?.layerDef?.minzoomVisible, | 
					
						
							|  |  |  |                             layer?.layerDef?.minzoom, | 
					
						
							|  |  |  |                             ")" | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     layer.isDisplayed.setData(loadData && isDisplayed) | 
					
						
							|  |  |  |                     if (key === "force" && loadData) { | 
					
						
							|  |  |  |                         layer.layerDef.minzoom = 0 | 
					
						
							|  |  |  |                         layer.layerDef.minzoomVisible = 0 | 
					
						
							|  |  |  |                         layer.isDisplayed.addCallback((isDisplayed) => { | 
					
						
							|  |  |  |                             if (!isDisplayed) { | 
					
						
							|  |  |  |                                 console.warn("Forcing layer " + paramsKey + " as true") | 
					
						
							|  |  |  |                                 layer.isDisplayed.setData(true) | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                         }) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             const pngCreator = new PngMapCreator(state, { | 
					
						
							|  |  |  |                 width: 4 * width, | 
					
						
							|  |  |  |                 height: 4 * height, | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             png = await pngCreator.CreatePng(this.options.freeComponentId, this._state) | 
					
						
							| 
									
										
										
										
											2023-06-04 23:58:29 +02:00
										 |  |  |             if (!png) { | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |                 throw "PngCreator did not output anything..." | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         svgImage.setAttribute("xlink:href", await SvgToPdfPage.blobToBase64(png)) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         smallestRect.parentElement.insertBefore(svgImage, smallestRect) | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         await this.prepareElement(svgImage, [], false) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  |         const smallestRectCss = SvgToPdfInternals.parseCss(smallestRect.getAttribute("style")) | 
					
						
							|  |  |  |         smallestRectCss["fill-opacity"] = "0" | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         smallestRect.setAttribute( | 
					
						
							|  |  |  |             "style", | 
					
						
							|  |  |  |             Object.keys(smallestRectCss) | 
					
						
							|  |  |  |                 .map((k) => k + ":" + smallestRectCss[k]) | 
					
						
							|  |  |  |                 .join(";") | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-14 12:18:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         textElement.parentElement.removeChild(textElement) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | export interface PdfTemplateInfo { | 
					
						
							|  |  |  |     pages: string[] | 
					
						
							|  |  |  |     description: string | Translation | 
					
						
							|  |  |  |     format: "a3" | "a4" | "a2" | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |     orientation: "portrait" | "landscape" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     isPublic: boolean | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  | export class SvgToPdf { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     public static readonly templates: Record< | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         "flyer_a4" | "poster_a3" | "poster_a2" | "current_view_a4" | "current_view_a3", | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         PdfTemplateInfo | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     > = { | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |         flyer_a4: { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             pages: [ | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |                 "./assets/templates/MapComplete-flyer.svg", | 
					
						
							|  |  |  |                 "./assets/templates/MapComplete-flyer.back.svg", | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             ], | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             format: "a4", | 
					
						
							|  |  |  |             orientation: "landscape", | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             description: Translations.t.flyer.description, | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             isPublic: false, | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |         }, | 
					
						
							|  |  |  |         poster_a3: { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             format: "a3", | 
					
						
							|  |  |  |             orientation: "portrait", | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             pages: ["./assets/templates/MapComplete-poster-a3.svg"], | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             description: "A basic A3 poster (similar to the flyer)", | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             isPublic: false, | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |         }, | 
					
						
							|  |  |  |         poster_a2: { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             format: "a2", | 
					
						
							|  |  |  |             orientation: "portrait", | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |             pages: ["./assets/templates/MapComplete-poster-a2.svg"], | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             description: "A basic A2 poster (similar to the flyer); scaled up from the A3 poster", | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             isPublic: false, | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |         current_view_a4: { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             format: "a4", | 
					
						
							|  |  |  |             orientation: "landscape", | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             pages: ["./assets/templates/CurrentMapWithHeaderA4.svg"], | 
					
						
							| 
									
										
										
										
											2023-06-04 23:58:29 +02:00
										 |  |  |             description: Translations.t.general.download.pdf.current_view_a4, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             isPublic: true, | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |         }, | 
					
						
							|  |  |  |         current_view_a3: { | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |             format: "a3", | 
					
						
							|  |  |  |             orientation: "portrait", | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |             pages: ["./assets/templates/CurrentMapWithHeaderA3.svg"], | 
					
						
							| 
									
										
										
										
											2023-06-04 23:58:29 +02:00
										 |  |  |             description: Translations.t.general.download.pdf.current_view_a3, | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             isPublic: true, | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |     public readonly status: Store<string> | 
					
						
							|  |  |  |     public readonly _status: UIEventSource<string> | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     private readonly _title: string | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |     private readonly _pages: SvgToPdfPage[] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |     constructor(title: string, pages: string[], options: SvgToPdfOptions) { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         this._title = title | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  |         options.textSubstitutions = options.textSubstitutions ?? {} | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         options.textSubstitutions["mapCount"] = | 
					
						
							|  |  |  |             "" + | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             Array.from(AllKnownLayouts.allKnownLayouts.values()).filter( | 
					
						
							|  |  |  |                 (th) => !th.hideFromOverview | 
					
						
							|  |  |  |             ).length | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         const state = new UIEventSource<string>("Initializing...") | 
					
						
							|  |  |  |         this.status = state | 
					
						
							|  |  |  |         this._status = state | 
					
						
							| 
									
										
										
										
											2023-06-07 00:14:20 +02:00
										 |  |  |         this._pages = pages.map((page) => new SvgToPdfPage(page, state, options, this._status)) | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Construct the PDF (including the maps to create), offers them to the user to downlaod. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public async ExportPdf(language: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         console.log("Building svg...") | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |         const firstPage = this._pages[0]._svgRoot | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |         const width = SvgToPdfInternals.attrNumber(firstPage, "width") | 
					
						
							|  |  |  |         const height = SvgToPdfInternals.attrNumber(firstPage, "height") | 
					
						
							|  |  |  |         const mode = width > height ? "landscape" : "portrait" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-04 00:43:32 +02:00
										 |  |  |         await this.Prepare(language) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-05 02:03:41 +02:00
										 |  |  |         this._status.setData("Maps are rendered, building pdf") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-17 21:35:56 +02:00
										 |  |  |         const doc = new jsPDF(mode, undefined, [width, height]) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         doc.advancedAPI((advancedApi) => { | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |             for (let i = 0; i < this._pages.length; i++) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 this._status.set("Rendering page " + i) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 if (i > 0) { | 
					
						
							| 
									
										
										
										
											2022-09-17 21:35:56 +02:00
										 |  |  |                     const page = this._pages[i]._svgRoot | 
					
						
							|  |  |  |                     const width = SvgToPdfInternals.attrNumber(page, "width") | 
					
						
							|  |  |  |                     const height = SvgToPdfInternals.attrNumber(page, "height") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     advancedApi.addPage([width, height]) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                     const mediabox: { | 
					
						
							|  |  |  |                         bottomLeftX: number | 
					
						
							|  |  |  |                         bottomLeftY: number | 
					
						
							|  |  |  |                         topRightX: number | 
					
						
							|  |  |  |                         topRightY: number | 
					
						
							|  |  |  |                     } = advancedApi.getCurrentPageInfo().pageContext.mediaBox | 
					
						
							| 
									
										
										
										
											2022-09-16 02:08:28 +02:00
										 |  |  |                     const targetWidth = 297 | 
					
						
							|  |  |  |                     const targetHeight = 210 | 
					
						
							|  |  |  |                     const sx = mediabox.topRightX / targetWidth | 
					
						
							|  |  |  |                     const sy = mediabox.topRightY / targetHeight | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |                     advancedApi.setCurrentTransformationMatrix( | 
					
						
							|  |  |  |                         advancedApi.Matrix(sx, 0, 0, -sy, 0, mediabox.topRightY) | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |                 this._pages[i].drawPage(advancedApi, i, language) | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         await doc.save(this._title + "." + language + ".pdf") | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |     public translationKeys(): Set<string> { | 
					
						
							|  |  |  |         const allTranslations = this._pages[0].extractTranslations() | 
					
						
							|  |  |  |         for (let i = 1; i < this._pages.length; i++) { | 
					
						
							|  |  |  |             const translations = this._pages[i].extractTranslations() | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             translations.forEach((t) => allTranslations.add(t)) | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         allTranslations.delete("import") | 
					
						
							|  |  |  |         allTranslations.delete("version") | 
					
						
							|  |  |  |         return allTranslations | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |     getTranslation(translationKey: string, language: string, strict: boolean = false) { | 
					
						
							|  |  |  |         for (const page of this._pages) { | 
					
						
							|  |  |  |             const tr = page.extractTranslation(translationKey, language, strict) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             if (tr === undefined) { | 
					
						
							| 
									
										
										
										
											2022-09-23 01:28:53 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             if (tr === translationKey) { | 
					
						
							| 
									
										
										
										
											2022-09-23 01:28:53 +02:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-23 01:28:53 +02:00
										 |  |  |             return tr | 
					
						
							| 
									
										
										
										
											2022-09-22 16:21:07 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         return undefined | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Prepares all the minimaps | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private async Prepare(language1: string): Promise<SvgToPdf> { | 
					
						
							|  |  |  |         for (const page of this._pages) { | 
					
						
							|  |  |  |             await page.Prepare() | 
					
						
							|  |  |  |             await page.PrepareLanguage(language1) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return this | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-12 20:14:03 +02:00
										 |  |  | } |