forked from MapComplete/MapComplete
Housekeeping...
This commit is contained in:
parent
707b25529b
commit
6d822b42ca
158 changed files with 7939 additions and 11272 deletions
|
@ -1,25 +1,28 @@
|
|||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
||||
import MinimapImplementation from "../UI/Base/MinimapImplementation";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import Loc from "../Models/Loc";
|
||||
import ShowDataLayer from "../UI/ShowDataLayer/ShowDataLayer";
|
||||
import {BBox} from "../Logic/BBox";
|
||||
import Minimap from "../UI/Base/Minimap";
|
||||
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers";
|
||||
import {Utils} from "../Utils";
|
||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState"
|
||||
import MinimapImplementation from "../UI/Base/MinimapImplementation"
|
||||
import { UIEventSource } from "../Logic/UIEventSource"
|
||||
import Loc from "../Models/Loc"
|
||||
import ShowDataLayer from "../UI/ShowDataLayer/ShowDataLayer"
|
||||
import { BBox } from "../Logic/BBox"
|
||||
import Minimap from "../UI/Base/Minimap"
|
||||
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
|
||||
import { Utils } from "../Utils"
|
||||
|
||||
export interface PngMapCreatorOptions{
|
||||
readonly divId: string; readonly width: number; readonly height: number; readonly scaling?: 1 | number,
|
||||
export interface PngMapCreatorOptions {
|
||||
readonly divId: string
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
readonly scaling?: 1 | number
|
||||
readonly dummyMode?: boolean
|
||||
}
|
||||
|
||||
export class PngMapCreator {
|
||||
private readonly _state: FeaturePipelineState | undefined;
|
||||
private readonly _options: PngMapCreatorOptions;
|
||||
private readonly _state: FeaturePipelineState | undefined
|
||||
private readonly _options: PngMapCreatorOptions
|
||||
|
||||
constructor(state: FeaturePipelineState | undefined, options: PngMapCreatorOptions) {
|
||||
this._state = state;
|
||||
this._options = {...options, scaling: options.scaling ?? 1};
|
||||
this._state = state
|
||||
this._options = { ...options, scaling: options.scaling ?? 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,10 +30,13 @@ export class PngMapCreator {
|
|||
* @private
|
||||
*/
|
||||
private async createAndLoadMinimap(): Promise<MinimapImplementation> {
|
||||
const state = this._state;
|
||||
const state = this._state
|
||||
const options = this._options
|
||||
const baselayer = AvailableBaseLayers.layerOverview.find(bl => bl.id === state.layoutToUse.defaultBackgroundId) ?? AvailableBaseLayers.osmCarto
|
||||
return new Promise(resolve => {
|
||||
const baselayer =
|
||||
AvailableBaseLayers.layerOverview.find(
|
||||
(bl) => bl.id === state.layoutToUse.defaultBackgroundId
|
||||
) ?? AvailableBaseLayers.osmCarto
|
||||
return new Promise((resolve) => {
|
||||
const minimap = Minimap.createMiniMap({
|
||||
location: new UIEventSource<Loc>(state.locationControl.data), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot
|
||||
background: new UIEventSource(baselayer),
|
||||
|
@ -38,24 +44,24 @@ export class PngMapCreator {
|
|||
onFullyLoaded: (_) =>
|
||||
window.setTimeout(() => {
|
||||
resolve(<MinimapImplementation>minimap)
|
||||
}, 250)
|
||||
}, 250),
|
||||
})
|
||||
const style = `width: ${options.width * options.scaling}mm; height: ${options.height * options.scaling}mm;`
|
||||
const style = `width: ${options.width * options.scaling}mm; height: ${
|
||||
options.height * options.scaling
|
||||
}mm;`
|
||||
minimap.SetStyle(style)
|
||||
minimap.AttachTo(options.divId)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a base64-encoded PNG image
|
||||
* @constructor
|
||||
*/
|
||||
public async CreatePng(format: "image" ): Promise<string > ;
|
||||
public async CreatePng(format: "blob"): Promise<Blob> ;
|
||||
public async CreatePng(format: "image" | "blob"): Promise<string | Blob>;
|
||||
public async CreatePng(format: "image"): Promise<string>
|
||||
public async CreatePng(format: "blob"): Promise<Blob>
|
||||
public async CreatePng(format: "image" | "blob"): Promise<string | Blob>
|
||||
public async CreatePng(format: "image" | "blob"): Promise<string | Blob> {
|
||||
|
||||
// Lets first init the minimap and wait for all background tiles to load
|
||||
const minimap = await this.createAndLoadMinimap()
|
||||
const state = this._state
|
||||
|
@ -67,9 +73,11 @@ export class PngMapCreator {
|
|||
if (dummyMode) {
|
||||
console.warn("Dummy mode is active - not loading map layers")
|
||||
} else {
|
||||
const bounds = BBox.fromLeafletBounds(leaflet.getBounds().pad(0.1).pad(-state.layoutToUse.widenFactor))
|
||||
const bounds = BBox.fromLeafletBounds(
|
||||
leaflet.getBounds().pad(0.1).pad(-state.layoutToUse.widenFactor)
|
||||
)
|
||||
state.currentBounds.setData(bounds)
|
||||
if(!state.featurePipeline.sufficientlyZoomed.data){
|
||||
if (!state.featurePipeline.sufficientlyZoomed.data) {
|
||||
console.warn("Not sufficiently zoomed!")
|
||||
}
|
||||
|
||||
|
@ -77,7 +85,9 @@ export class PngMapCreator {
|
|||
// A query is running!
|
||||
// Let's wait for it to complete
|
||||
console.log("Waiting for the query to complete")
|
||||
await state.featurePipeline.runningQuery.AsPromise(isRunning => !isRunning)
|
||||
await state.featurePipeline.runningQuery.AsPromise(
|
||||
(isRunning) => !isRunning
|
||||
)
|
||||
console.log("Query has completeted!")
|
||||
}
|
||||
|
||||
|
@ -96,15 +106,22 @@ export class PngMapCreator {
|
|||
})
|
||||
await Utils.waitFor(2000)
|
||||
}
|
||||
minimap.TakeScreenshot(format).then(async result => {
|
||||
const divId = this._options.divId
|
||||
await Utils.waitFor(250)
|
||||
document.getElementById(divId).removeChild(/*Will fetch the cached htmlelement:*/minimap.ConstructElement())
|
||||
return resolve(result);
|
||||
}).catch(failreason => {
|
||||
console.error("Could no make a screenshot due to ",failreason)
|
||||
reject(failreason)
|
||||
})
|
||||
minimap
|
||||
.TakeScreenshot(format)
|
||||
.then(async (result) => {
|
||||
const divId = this._options.divId
|
||||
await Utils.waitFor(250)
|
||||
document
|
||||
.getElementById(divId)
|
||||
.removeChild(
|
||||
/*Will fetch the cached htmlelement:*/ minimap.ConstructElement()
|
||||
)
|
||||
return resolve(result)
|
||||
})
|
||||
.catch((failreason) => {
|
||||
console.error("Could no make a screenshot due to ", failreason)
|
||||
reject(failreason)
|
||||
})
|
||||
})
|
||||
|
||||
state.AddAllOverlaysToMap(minimap.leafletMap)
|
||||
|
|
|
@ -1,43 +1,48 @@
|
|||
import jsPDF, {Matrix} from "jspdf";
|
||||
import {Translation, TypedTranslation} from "../UI/i18n/Translation";
|
||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
||||
import {PngMapCreator} from "./pngMapCreator";
|
||||
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
||||
import {Store} from "../Logic/UIEventSource";
|
||||
import jsPDF, { Matrix } from "jspdf"
|
||||
import { Translation, TypedTranslation } from "../UI/i18n/Translation"
|
||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState"
|
||||
import { PngMapCreator } from "./pngMapCreator"
|
||||
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts"
|
||||
import { Store } from "../Logic/UIEventSource"
|
||||
import "../assets/templates/Ubuntu-M-normal.js"
|
||||
import "../assets/templates/Ubuntu-L-normal.js"
|
||||
import "../assets/templates/UbuntuMono-B-bold.js"
|
||||
import {makeAbsolute, parseSVG} from 'svg-path-parser';
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
import {Utils} from "../Utils";
|
||||
import Constants from "../Models/Constants";
|
||||
import Hash from "../Logic/Web/Hash";
|
||||
import { makeAbsolute, parseSVG } from "svg-path-parser"
|
||||
import Translations from "../UI/i18n/Translations"
|
||||
import { Utils } from "../Utils"
|
||||
import Constants from "../Models/Constants"
|
||||
import Hash from "../Logic/Web/Hash"
|
||||
|
||||
class SvgToPdfInternals {
|
||||
private readonly doc: jsPDF;
|
||||
private readonly doc: jsPDF
|
||||
private static readonly dummyDoc: jsPDF = new jsPDF()
|
||||
private readonly matrices: Matrix[] = []
|
||||
private readonly matricesInverted: Matrix[] = []
|
||||
|
||||
private currentMatrix: Matrix;
|
||||
private currentMatrixInverted: Matrix;
|
||||
private currentMatrix: Matrix
|
||||
private currentMatrixInverted: Matrix
|
||||
|
||||
private readonly _images: Record<string, HTMLImageElement>;
|
||||
private readonly _rects: Record<string, SVGRectElement>;
|
||||
private readonly extractTranslation: (string) => string;
|
||||
private readonly _images: Record<string, HTMLImageElement>
|
||||
private readonly _rects: Record<string, SVGRectElement>
|
||||
private readonly extractTranslation: (string) => string
|
||||
|
||||
constructor(advancedApi: jsPDF, images: Record<string, HTMLImageElement>, rects: Record<string, SVGRectElement>, extractTranslation: (string) => string) {
|
||||
this.doc = advancedApi;
|
||||
this._images = images;
|
||||
this._rects = rects;
|
||||
this.extractTranslation = s => extractTranslation(s)?.replace(/ /g, " ");
|
||||
this.currentMatrix = this.doc.unitMatrix;
|
||||
this.currentMatrixInverted = this.doc.unitMatrix;
|
||||
constructor(
|
||||
advancedApi: jsPDF,
|
||||
images: Record<string, HTMLImageElement>,
|
||||
rects: Record<string, SVGRectElement>,
|
||||
extractTranslation: (string) => string
|
||||
) {
|
||||
this.doc = advancedApi
|
||||
this._images = images
|
||||
this._rects = rects
|
||||
this.extractTranslation = (s) => extractTranslation(s)?.replace(/ /g, " ")
|
||||
this.currentMatrix = this.doc.unitMatrix
|
||||
this.currentMatrixInverted = this.doc.unitMatrix
|
||||
}
|
||||
|
||||
applyMatrices(): void {
|
||||
let multiplied = this.doc.unitMatrix;
|
||||
let multipliedInv = this.doc.unitMatrix;
|
||||
let multiplied = this.doc.unitMatrix
|
||||
let multipliedInv = this.doc.unitMatrix
|
||||
for (const matrix of this.matrices) {
|
||||
multiplied = this.doc.matrixMult(multiplied, matrix)
|
||||
}
|
||||
|
@ -51,21 +56,19 @@ class SvgToPdfInternals {
|
|||
addMatrix(m: Matrix) {
|
||||
this.matrices.push(m)
|
||||
this.matricesInverted.push(m.inversed())
|
||||
this.doc.setCurrentTransformationMatrix(m);
|
||||
this.doc.setCurrentTransformationMatrix(m)
|
||||
this.applyMatrices()
|
||||
|
||||
}
|
||||
|
||||
public static extractMatrix(element: Element): Matrix {
|
||||
|
||||
const t = element.getAttribute("transform")
|
||||
if (t === null) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
const scaleMatch = t.match(/scale\(([-0-9.]+)\)/)
|
||||
if (scaleMatch !== null) {
|
||||
const s = Number(scaleMatch[1])
|
||||
return SvgToPdfInternals.dummyDoc.Matrix(1 / s, 0, 0, 1 / s, 0, 0);
|
||||
return SvgToPdfInternals.dummyDoc.Matrix(1 / s, 0, 0, 1 / s, 0, 0)
|
||||
}
|
||||
|
||||
const translateMatch = t.match(/translate\(([-0-9.]+), ?([-0-9.]*)\)/)
|
||||
|
@ -73,11 +76,12 @@ class SvgToPdfInternals {
|
|||
const dx = Number(translateMatch[1])
|
||||
const dy = Number(translateMatch[2])
|
||||
console.log("Translating", dx, dy)
|
||||
return SvgToPdfInternals.dummyDoc.Matrix(1, 0, 0, 1, dx, dy);
|
||||
return SvgToPdfInternals.dummyDoc.Matrix(1, 0, 0, 1, dx, dy)
|
||||
}
|
||||
|
||||
|
||||
const transformMatch = t.match(/matrix\(([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*)\)/)
|
||||
const transformMatch = t.match(
|
||||
/matrix\(([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*),([-0-9.]*)\)/
|
||||
)
|
||||
if (transformMatch !== null) {
|
||||
const vals = [1, 0, 0, 1, 0, 0]
|
||||
const invVals = [1, 0, 0, 1, 0, 0]
|
||||
|
@ -90,20 +94,26 @@ class SvgToPdfInternals {
|
|||
vals[i] = ti
|
||||
}
|
||||
}
|
||||
return SvgToPdfInternals.dummyDoc.Matrix(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
|
||||
return SvgToPdfInternals.dummyDoc.Matrix(
|
||||
vals[0],
|
||||
vals[1],
|
||||
vals[2],
|
||||
vals[3],
|
||||
vals[4],
|
||||
vals[5]
|
||||
)
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
public setTransform(element: Element): boolean {
|
||||
|
||||
const m = SvgToPdfInternals.extractMatrix(element)
|
||||
if (m === null) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
this.addMatrix(m)
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
public undoTransform(): void {
|
||||
|
@ -111,7 +121,6 @@ class SvgToPdfInternals {
|
|||
const i = this.matricesInverted.pop()
|
||||
this.doc.setCurrentTransformationMatrix(i)
|
||||
this.applyMatrices()
|
||||
|
||||
}
|
||||
|
||||
public static parseCss(styleContent: string, separator: string = ";"): Record<string, string> {
|
||||
|
@ -121,12 +130,12 @@ class SvgToPdfInternals {
|
|||
const r: Record<string, string> = {}
|
||||
|
||||
for (const rule of styleContent.split(separator)) {
|
||||
const [k, v] = rule.split(":").map(x => x.trim())
|
||||
const [k, v] = rule.split(":").map((x) => x.trim())
|
||||
r[k] = v
|
||||
}
|
||||
|
||||
return r
|
||||
};
|
||||
}
|
||||
|
||||
private drawRect(element: SVGRectElement) {
|
||||
const x = Number(element.getAttribute("x"))
|
||||
|
@ -156,19 +165,20 @@ class SvgToPdfInternals {
|
|||
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
|
||||
}
|
||||
|
||||
|
||||
private static attr(element: Element, name: string, recurseup: boolean = true): string | undefined {
|
||||
private static attr(
|
||||
element: Element,
|
||||
name: string,
|
||||
recurseup: boolean = true
|
||||
): string | undefined {
|
||||
if (element === null || element === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -188,22 +198,20 @@ class SvgToPdfInternals {
|
|||
* @private
|
||||
*/
|
||||
private static css(element: Element): Record<string, string> {
|
||||
|
||||
if (element.parentElement == undefined || element.parentElement == element) {
|
||||
return SvgToPdfInternals.parseCss(element.getAttribute("style"))
|
||||
}
|
||||
|
||||
const css = SvgToPdfInternals.css(element.parentElement);
|
||||
const css = SvgToPdfInternals.css(element.parentElement)
|
||||
const style = element.getAttribute("style")
|
||||
if (style === undefined || style == null) {
|
||||
return css
|
||||
}
|
||||
for (const rule of style.split(";")) {
|
||||
const [k, v] = rule.split(":").map(x => x.trim())
|
||||
const [k, v] = rule.split(":").map((x) => x.trim())
|
||||
css[k] = v
|
||||
}
|
||||
return css
|
||||
|
||||
}
|
||||
|
||||
static attrNumber(element: Element, name: string, recurseup: boolean = true): number {
|
||||
|
@ -233,15 +241,14 @@ class SvgToPdfInternals {
|
|||
}
|
||||
}
|
||||
|
||||
let fontFamily = css["font-family"] ?? "Ubuntu";
|
||||
let fontFamily = css["font-family"] ?? "Ubuntu"
|
||||
if (fontFamily === "sans-serif") {
|
||||
fontFamily = "Ubuntu"
|
||||
}
|
||||
|
||||
let fontWeight = css["font-weight"] ?? "normal";
|
||||
let fontWeight = css["font-weight"] ?? "normal"
|
||||
this.doc.setFont(fontFamily, fontWeight)
|
||||
|
||||
|
||||
const fontColor = css["fill"]
|
||||
if (fontColor) {
|
||||
this.doc.setTextColor(fontColor)
|
||||
|
@ -279,13 +286,13 @@ class SvgToPdfInternals {
|
|||
if (list) {
|
||||
const key = list[1]
|
||||
console.log("Generating a list with key" + key)
|
||||
let r = this.extractTranslation("$" + key + "0");
|
||||
let r = this.extractTranslation("$" + key + "0")
|
||||
let i = 0
|
||||
result += "\n"
|
||||
while (r !== undefined && i < 100) {
|
||||
result += "• " + r + "\n"
|
||||
i++
|
||||
r = this.extractTranslation("$" + key + i);
|
||||
r = this.extractTranslation("$" + key + i)
|
||||
}
|
||||
result += "\n"
|
||||
addSpace = false
|
||||
|
@ -298,10 +305,15 @@ class SvgToPdfInternals {
|
|||
addSpace = true
|
||||
}
|
||||
}
|
||||
this.doc.text(result, x, y, {
|
||||
maxWidth,
|
||||
}, this.currentMatrix)
|
||||
|
||||
this.doc.text(
|
||||
result,
|
||||
x,
|
||||
y,
|
||||
{
|
||||
maxWidth,
|
||||
},
|
||||
this.currentMatrix
|
||||
)
|
||||
}
|
||||
|
||||
private drawSvgViaCanvas(element: Element): void {
|
||||
|
@ -310,14 +322,13 @@ class SvgToPdfInternals {
|
|||
const width = SvgToPdfInternals.attrNumber(element, "width")
|
||||
const height = SvgToPdfInternals.attrNumber(element, "height")
|
||||
const base64src = SvgToPdfInternals.attr(element, "xlink:href")
|
||||
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];
|
||||
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]
|
||||
const svgWidth = SvgToPdfInternals.attrNumber(svgRoot, "width")
|
||||
const svgHeight = SvgToPdfInternals.attrNumber(svgRoot, "height")
|
||||
|
||||
|
||||
let img = this._images[base64src]
|
||||
// This is an svg image, we use the canvas to convert it to a png
|
||||
const canvas = document.createElement("canvas")
|
||||
|
@ -325,22 +336,29 @@ class SvgToPdfInternals {
|
|||
|
||||
canvas.width = svgWidth
|
||||
canvas.height = svgHeight
|
||||
img.style.width = `${(svgWidth)}px`
|
||||
img.style.height = `${(svgHeight)}px`
|
||||
img.style.width = `${svgWidth}px`
|
||||
img.style.height = `${svgHeight}px`
|
||||
|
||||
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))
|
||||
const p = this.currentMatrixInverted.applyToPoint({x, y})
|
||||
this.doc.addImage(base64img, "png", p.x * svgWidth / width, p.y * svgHeight / height, svgWidth, svgHeight)
|
||||
const p = this.currentMatrixInverted.applyToPoint({ x, y })
|
||||
this.doc.addImage(
|
||||
base64img,
|
||||
"png",
|
||||
(p.x * svgWidth) / width,
|
||||
(p.y * svgHeight) / height,
|
||||
svgWidth,
|
||||
svgHeight
|
||||
)
|
||||
this.undoTransform()
|
||||
}
|
||||
|
||||
private drawImage(element: Element): void {
|
||||
const href = SvgToPdfInternals.attr(element, "xlink:href")
|
||||
if (href.endsWith('svg') || href.startsWith("data:image/svg")) {
|
||||
this.drawSvgViaCanvas(element);
|
||||
if (href.endsWith("svg") || href.startsWith("data:image/svg")) {
|
||||
this.drawSvgViaCanvas(element)
|
||||
} else {
|
||||
const x = SvgToPdfInternals.attrNumber(element, "x")
|
||||
const y = SvgToPdfInternals.attrNumber(element, "y")
|
||||
|
@ -354,31 +372,31 @@ class SvgToPdfInternals {
|
|||
|
||||
private drawPath(element: SVGPathElement): void {
|
||||
const path = element.getAttribute("d")
|
||||
const parsed: { code: string, x: number, y: number, x2?, y2?, x1?, y1? }[] = parseSVG(path)
|
||||
const parsed: { code: string; x: number; y: number; x2?; y2?; x1?; y1? }[] = parseSVG(path)
|
||||
makeAbsolute(parsed)
|
||||
|
||||
for (const c of parsed) {
|
||||
if (c.code === "C" || c.code === "c") {
|
||||
const command = {op: "c", c: [c.x1, c.y1, c.x2, c.y2, c.x, c.y]}
|
||||
const command = { op: "c", c: [c.x1, c.y1, c.x2, c.y2, c.x, c.y] }
|
||||
this.doc.path([command])
|
||||
continue
|
||||
}
|
||||
|
||||
if (c.code === "H") {
|
||||
const command = {op: "l", c: [c.x, c.y]}
|
||||
const command = { op: "l", c: [c.x, c.y] }
|
||||
this.doc.path([command])
|
||||
continue
|
||||
}
|
||||
|
||||
if (c.code === "V") {
|
||||
const command = {op: "l", c: [c.x, c.y]}
|
||||
const command = { op: "l", c: [c.x, c.y] }
|
||||
this.doc.path([command])
|
||||
continue
|
||||
}
|
||||
|
||||
this.doc.path([{op: c.code.toLowerCase(), c: [c.x, c.y]}])
|
||||
this.doc.path([{ op: c.code.toLowerCase(), c: [c.x, c.y] }])
|
||||
}
|
||||
//"fill:#ffffff;stroke:#000000;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:20"
|
||||
//"fill:#ffffff;stroke:#000000;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:20"
|
||||
|
||||
const css = SvgToPdfInternals.css(element)
|
||||
if (css["color"] && css["color"].toLowerCase() !== "none") {
|
||||
|
@ -413,7 +431,6 @@ class SvgToPdfInternals {
|
|||
public handleElement(element: SVGSVGElement | Element): void {
|
||||
const isTransformed = this.setTransform(element)
|
||||
try {
|
||||
|
||||
if (element.tagName === "tspan") {
|
||||
if (element.childElementCount == 0) {
|
||||
this.drawTspan(element)
|
||||
|
@ -433,7 +450,6 @@ class SvgToPdfInternals {
|
|||
}
|
||||
|
||||
if (element.tagName === "g" || element.tagName === "text") {
|
||||
|
||||
for (let child of Array.from(element.children)) {
|
||||
this.handleElement(child)
|
||||
}
|
||||
|
@ -446,7 +462,6 @@ class SvgToPdfInternals {
|
|||
if (element.tagName === "circle") {
|
||||
this.drawCircle(<any>element)
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error("Could not handle element", element, "due to", e)
|
||||
}
|
||||
|
@ -461,7 +476,7 @@ class SvgToPdfInternals {
|
|||
* @param mapSpec
|
||||
* @constructor
|
||||
*/
|
||||
static GetActualXY(mapSpec: SVGTSpanElement): { x: number, y: number } {
|
||||
static GetActualXY(mapSpec: SVGTSpanElement): { x: number; y: number } {
|
||||
let runningM = SvgToPdfInternals.dummyDoc.unitMatrix
|
||||
|
||||
let e: Element = mapSpec
|
||||
|
@ -475,35 +490,32 @@ class SvgToPdfInternals {
|
|||
|
||||
const x = SvgToPdfInternals.attrNumber(mapSpec, "x")
|
||||
const y = SvgToPdfInternals.attrNumber(mapSpec, "y")
|
||||
return runningM.applyToPoint({x, y})
|
||||
return runningM.applyToPoint({ x, y })
|
||||
}
|
||||
}
|
||||
|
||||
export interface SvgToPdfOptions {
|
||||
getFreeDiv: () => string,
|
||||
getFreeDiv: () => string
|
||||
disableMaps?: false | true
|
||||
textSubstitutions?: Record<string, string>,
|
||||
beforePage?: (i: number) => void,
|
||||
overrideLocation?: { lat: number, lon: number }
|
||||
textSubstitutions?: Record<string, string>
|
||||
beforePage?: (i: number) => void
|
||||
overrideLocation?: { lat: number; lon: number }
|
||||
}
|
||||
|
||||
|
||||
export class SvgToPdfPage {
|
||||
|
||||
private images: Record<string, HTMLImageElement> = {}
|
||||
private rects: Record<string, SVGRectElement> = {}
|
||||
public readonly _svgRoot: SVGSVGElement;
|
||||
public readonly _svgRoot: SVGSVGElement
|
||||
public readonly currentState: Store<string>
|
||||
private readonly importedTranslations: Record<string, string> = {}
|
||||
private readonly layerTranslations: Record<string, Record<string, any>> = {}
|
||||
private readonly options: SvgToPdfOptions
|
||||
|
||||
constructor(page: string, options?: SvgToPdfOptions) {
|
||||
this.options = options ?? (<SvgToPdfOptions>{})
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(page, "image/svg+xml");
|
||||
this._svgRoot = xmlDoc.getElementsByTagName("svg")[0];
|
||||
|
||||
this.options = options ?? <SvgToPdfOptions>{}
|
||||
const parser = new DOMParser()
|
||||
const xmlDoc = parser.parseFromString(page, "image/svg+xml")
|
||||
this._svgRoot = xmlDoc.getElementsByTagName("svg")[0]
|
||||
}
|
||||
|
||||
private loadImage(element: Element): Promise<void> {
|
||||
|
@ -511,11 +523,13 @@ export class SvgToPdfPage {
|
|||
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 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)
|
||||
|
@ -533,20 +547,20 @@ export class SvgToPdfPage {
|
|||
|
||||
this.images[xlink] = img
|
||||
return new Promise((resolve) => {
|
||||
img.onload = _ => {
|
||||
img.onload = (_) => {
|
||||
resolve()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
public extractTranslations(): Set<string> {
|
||||
const textContents: string[] = Array.from(this._svgRoot.getElementsByTagName("tspan"))
|
||||
.map(t => t.textContent)
|
||||
const textContents: string[] = Array.from(this._svgRoot.getElementsByTagName("tspan")).map(
|
||||
(t) => t.textContent
|
||||
)
|
||||
const translations = new Set<string>()
|
||||
console.log("Extracting translations, contents are", textContents)
|
||||
for (const tc of textContents) {
|
||||
const parts = tc.split(" ").filter(p => p.startsWith("$") && p.indexOf("(") < 0)
|
||||
const parts = tc.split(" ").filter((p) => p.startsWith("$") && p.indexOf("(") < 0)
|
||||
for (let part of parts) {
|
||||
part = part.substring(1) // Drop the $
|
||||
let path = part.split(".")
|
||||
|
@ -562,9 +576,12 @@ export class SvgToPdfPage {
|
|||
return translations
|
||||
}
|
||||
|
||||
public async prepareElement(element: SVGSVGElement | Element, mapTextSpecs: SVGTSpanElement[]): Promise<void> {
|
||||
public async prepareElement(
|
||||
element: SVGSVGElement | Element,
|
||||
mapTextSpecs: SVGTSpanElement[]
|
||||
): Promise<void> {
|
||||
if (element.tagName === "rect") {
|
||||
this.rects[element.id] = <SVGRectElement>element;
|
||||
this.rects[element.id] = <SVGRectElement>element
|
||||
}
|
||||
|
||||
if (element.tagName === "image") {
|
||||
|
@ -572,17 +589,26 @@ export class SvgToPdfPage {
|
|||
}
|
||||
|
||||
if (element.tagName === "tspan" && element.childElementCount == 0) {
|
||||
const specialValues = element.textContent.split(" ").filter(t => t.startsWith("$"))
|
||||
const specialValues = element.textContent.split(" ").filter((t) => t.startsWith("$"))
|
||||
for (let specialValue of specialValues) {
|
||||
const importMatch = element.textContent.match(/\$import ([a-zA-Z-_0-9.? ]+) as ([a-zA-Z0-9]+)/)
|
||||
const importMatch = element.textContent.match(
|
||||
/\$import ([a-zA-Z-_0-9.? ]+) as ([a-zA-Z0-9]+)/
|
||||
)
|
||||
if (importMatch !== null) {
|
||||
const [, pathRaw, as] = importMatch
|
||||
this.importedTranslations[as] = pathRaw
|
||||
}
|
||||
const setPropertyMatch = element.textContent.match(/\$set\(([a-zA-Z-_0-9.?:]+),(.+)\)/)
|
||||
const setPropertyMatch = element.textContent.match(
|
||||
/\$set\(([a-zA-Z-_0-9.?:]+),(.+)\)/
|
||||
)
|
||||
if (setPropertyMatch) {
|
||||
this.options.textSubstitutions[setPropertyMatch[1].trim()] = setPropertyMatch[2].trim()
|
||||
console.log("Setting a property:", setPropertyMatch, this.options.textSubstitutions)
|
||||
this.options.textSubstitutions[setPropertyMatch[1].trim()] =
|
||||
setPropertyMatch[2].trim()
|
||||
console.log(
|
||||
"Setting a property:",
|
||||
setPropertyMatch,
|
||||
this.options.textSubstitutions
|
||||
)
|
||||
}
|
||||
if (element.textContent.startsWith("$map(")) {
|
||||
mapTextSpecs.push(<any>element)
|
||||
|
@ -590,20 +616,23 @@ export class SvgToPdfPage {
|
|||
}
|
||||
}
|
||||
|
||||
if (element.tagName === "g" || element.tagName === "text" || element.tagName === "tspan" || element.tagName === "defs") {
|
||||
|
||||
if (
|
||||
element.tagName === "g" ||
|
||||
element.tagName === "text" ||
|
||||
element.tagName === "tspan" ||
|
||||
element.tagName === "defs"
|
||||
) {
|
||||
for (let child of Array.from(element.children)) {
|
||||
await this.prepareElement(child, mapTextSpecs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _isPrepared = false;
|
||||
private _isPrepared = false
|
||||
|
||||
private async prepareMap(mapSpec: SVGTSpanElement,): Promise<void> {
|
||||
private async prepareMap(mapSpec: SVGTSpanElement): Promise<void> {
|
||||
// Upper left point of the tspan
|
||||
const {x, y} = SvgToPdfInternals.GetActualXY(mapSpec)
|
||||
const { x, y } = SvgToPdfInternals.GetActualXY(mapSpec)
|
||||
|
||||
let textElement: Element = mapSpec
|
||||
// We recurse up to get the actual, full specification
|
||||
|
@ -618,7 +647,7 @@ export class SvgToPdfPage {
|
|||
const params = SvgToPdfInternals.parseCss(match[1], ",")
|
||||
|
||||
let smallestRect: SVGRectElement = undefined
|
||||
let smallestSurface: number = 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) {
|
||||
const rect = this.rects[id]
|
||||
|
@ -635,14 +664,17 @@ export class SvgToPdfPage {
|
|||
smallestSurface = surface
|
||||
smallestRect = rect
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (smallestRect === undefined) {
|
||||
throw "No rectangle found around " + spec + ". Draw a rectangle around it, the map will be projected on that one"
|
||||
throw (
|
||||
"No rectangle found around " +
|
||||
spec +
|
||||
". Draw a rectangle around it, the map will be projected on that one"
|
||||
)
|
||||
}
|
||||
|
||||
const svgImage = document.createElement('image')
|
||||
const svgImage = document.createElement("image")
|
||||
svgImage.setAttribute("x", smallestRect.getAttribute("x"))
|
||||
svgImage.setAttribute("y", smallestRect.getAttribute("y"))
|
||||
const width = SvgToPdfInternals.attrNumber(smallestRect, "width")
|
||||
|
@ -653,7 +685,9 @@ export class SvgToPdfPage {
|
|||
let layout = AllKnownLayouts.allKnownLayouts.get(params["theme"])
|
||||
if (layout === undefined) {
|
||||
console.error("Could not show map with parameters", params)
|
||||
throw "Theme not found:" + params["theme"] + ". Use theme: to define which theme to use. "
|
||||
throw (
|
||||
"Theme not found:" + params["theme"] + ". Use theme: to define which theme to use. "
|
||||
)
|
||||
}
|
||||
layout.widenFactor = 0
|
||||
layout.overpassTimeout = 600
|
||||
|
@ -662,7 +696,7 @@ export class SvgToPdfPage {
|
|||
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)
|
||||
const layer = layout.layers.find((l) => l.id === layerName)
|
||||
if (layer === undefined) {
|
||||
throw "No layer found for " + paramsKey
|
||||
}
|
||||
|
@ -672,7 +706,7 @@ export class SvgToPdfPage {
|
|||
}
|
||||
}
|
||||
}
|
||||
const zoom = Number(params["zoom"] ?? params["z"] ?? 14);
|
||||
const zoom = Number(params["zoom"] ?? params["z"] ?? 14)
|
||||
|
||||
Hash.hash.setData(undefined)
|
||||
// QueryParameters.ClearAll()
|
||||
|
@ -681,7 +715,7 @@ export class SvgToPdfPage {
|
|||
state.locationControl.setData({
|
||||
zoom,
|
||||
lat: this.options?.overrideLocation?.lat ?? Number(params["lat"] ?? 51.05016),
|
||||
lon: this.options?.overrideLocation?.lon ?? Number(params["lon"] ?? 3.717842)
|
||||
lon: this.options?.overrideLocation?.lon ?? Number(params["lon"] ?? 3.717842),
|
||||
})
|
||||
|
||||
console.log("Params are", params, params["layers"] === "none")
|
||||
|
@ -689,7 +723,9 @@ export class SvgToPdfPage {
|
|||
const fl = state.filteredLayers.data
|
||||
for (const filteredLayer of fl) {
|
||||
if (params["layer-" + filteredLayer.layerDef.id] !== undefined) {
|
||||
filteredLayer.isDisplayed.setData(params["layer-" + filteredLayer.layerDef.id].trim().toLowerCase() !== "false")
|
||||
filteredLayer.isDisplayed.setData(
|
||||
params["layer-" + filteredLayer.layerDef.id].trim().toLowerCase() !== "false"
|
||||
)
|
||||
} else if (params["layers"] === "none") {
|
||||
filteredLayer.isDisplayed.setData(false)
|
||||
} else if (filteredLayer.layerDef.id.startsWith("note_import")) {
|
||||
|
@ -701,16 +737,23 @@ export class SvgToPdfPage {
|
|||
if (paramsKey.startsWith("layer-")) {
|
||||
const layerName = paramsKey.substring("layer-".length)
|
||||
const key = params[paramsKey].toLowerCase().trim()
|
||||
const isDisplayed = key === "true" || key === "force";
|
||||
const layer = state.filteredLayers.data.find(l => l.layerDef.id === layerName)
|
||||
console.log("Setting ", layer?.layerDef?.id, " to visibility", isDisplayed, "(minzoom:", layer?.layerDef?.minzoomVisible, layer?.layerDef?.minzoom, ")")
|
||||
layer.isDisplayed.setData(
|
||||
isDisplayed
|
||||
const isDisplayed = key === "true" || key === "force"
|
||||
const layer = state.filteredLayers.data.find((l) => l.layerDef.id === layerName)
|
||||
console.log(
|
||||
"Setting ",
|
||||
layer?.layerDef?.id,
|
||||
" to visibility",
|
||||
isDisplayed,
|
||||
"(minzoom:",
|
||||
layer?.layerDef?.minzoomVisible,
|
||||
layer?.layerDef?.minzoom,
|
||||
")"
|
||||
)
|
||||
layer.isDisplayed.setData(isDisplayed)
|
||||
if (key === "force") {
|
||||
layer.layerDef.minzoom = 0
|
||||
layer.layerDef.minzoomVisible = 0
|
||||
layer.isDisplayed.addCallback(isDisplayed => {
|
||||
layer.isDisplayed.addCallback((isDisplayed) => {
|
||||
if (!isDisplayed) {
|
||||
console.warn("Forcing layer " + paramsKey + " as true")
|
||||
layer.isDisplayed.setData(true)
|
||||
|
@ -720,44 +763,53 @@ export class SvgToPdfPage {
|
|||
}
|
||||
}
|
||||
|
||||
const pngCreator = new PngMapCreator(
|
||||
state,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
scaling: Number(params["scaling"] ?? 1.5),
|
||||
divId: this.options.getFreeDiv(),
|
||||
dummyMode: this.options.disableMaps
|
||||
}
|
||||
)
|
||||
const pngCreator = new PngMapCreator(state, {
|
||||
width,
|
||||
height,
|
||||
scaling: Number(params["scaling"] ?? 1.5),
|
||||
divId: this.options.getFreeDiv(),
|
||||
dummyMode: this.options.disableMaps,
|
||||
})
|
||||
const png = await pngCreator.CreatePng("image")
|
||||
|
||||
svgImage.setAttribute('xlink:href', png)
|
||||
svgImage.setAttribute("xlink:href", png)
|
||||
smallestRect.parentElement.insertBefore(svgImage, smallestRect)
|
||||
await this.prepareElement(svgImage, [])
|
||||
|
||||
|
||||
const smallestRectCss = SvgToPdfInternals.parseCss(smallestRect.getAttribute("style"))
|
||||
smallestRectCss["fill-opacity"] = "0"
|
||||
smallestRect.setAttribute("style", Object.keys(smallestRectCss).map(k => k + ":" + smallestRectCss[k]).join(";"))
|
||||
|
||||
smallestRect.setAttribute(
|
||||
"style",
|
||||
Object.keys(smallestRectCss)
|
||||
.map((k) => k + ":" + smallestRectCss[k])
|
||||
.join(";")
|
||||
)
|
||||
|
||||
textElement.parentElement.removeChild(textElement)
|
||||
}
|
||||
|
||||
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/" + language + ".json", 24 * 60 * 60 * 1000)
|
||||
const shared_questions = await Utils.downloadJsonCached("https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/langs/shared-questions/" + language + ".json", 24 * 60 * 60 * 1000)
|
||||
this.layerTranslations[language] = await Utils.downloadJsonCached(
|
||||
"https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/langs/layers/" +
|
||||
language +
|
||||
".json",
|
||||
24 * 60 * 60 * 1000
|
||||
)
|
||||
const shared_questions = await Utils.downloadJsonCached(
|
||||
"https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/langs/shared-questions/" +
|
||||
language +
|
||||
".json",
|
||||
24 * 60 * 60 * 1000
|
||||
)
|
||||
this.layerTranslations[language]["shared-questions"] = shared_questions["shared_questions"]
|
||||
}
|
||||
|
||||
public async Prepare() {
|
||||
|
||||
if (this._isPrepared) {
|
||||
return
|
||||
}
|
||||
this._isPrepared = true;
|
||||
this._isPrepared = true
|
||||
const mapSpecs: SVGTSpanElement[] = []
|
||||
for (let child of Array.from(this._svgRoot.children)) {
|
||||
await this.prepareElement(<any>child, mapSpecs)
|
||||
|
@ -766,8 +818,6 @@ export class SvgToPdfPage {
|
|||
for (const mapSpec of mapSpecs) {
|
||||
await this.prepareMap(mapSpec)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public drawPage(advancedApi: jsPDF, i: number, language): void {
|
||||
|
@ -779,16 +829,21 @@ export class SvgToPdfPage {
|
|||
this.options.beforePage(i)
|
||||
}
|
||||
const self = this
|
||||
const internal = new SvgToPdfInternals(advancedApi, this.images, this.rects, key => self.extractTranslation(key, language));
|
||||
const internal = new SvgToPdfInternals(advancedApi, this.images, this.rects, (key) =>
|
||||
self.extractTranslation(key, language)
|
||||
)
|
||||
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
|
||||
return (
|
||||
new Date().toISOString().substring(0, "2022-01-02THH:MM".length) +
|
||||
" - v" +
|
||||
Constants.vNumber
|
||||
)
|
||||
}
|
||||
const pathPart = text.match(/\$(([_a-zA-Z0-9? ]+\.)+[_a-zA-Z0-9? ]+)(.*)/)
|
||||
if (pathPart === null) {
|
||||
|
@ -817,7 +872,7 @@ export class SvgToPdfPage {
|
|||
}
|
||||
|
||||
if (typeof t === "string") {
|
||||
t = new TypedTranslation({"*": t})
|
||||
t = new TypedTranslation({ "*": t })
|
||||
}
|
||||
if (t instanceof TypedTranslation) {
|
||||
if (strict && (t.translations[language] ?? t.translations["*"]) === undefined) {
|
||||
|
@ -833,40 +888,47 @@ export class SvgToPdfPage {
|
|||
console.error("Could not get textFor from ", t, "for path", text)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class SvgToPdf {
|
||||
|
||||
public static readonly templates: Record<string, { pages: string[], description: string | Translation }> = {
|
||||
public static readonly templates: Record<
|
||||
string,
|
||||
{ pages: string[]; description: string | Translation }
|
||||
> = {
|
||||
flyer_a4: {
|
||||
pages: ["/assets/templates/MapComplete-flyer.svg", "/assets/templates/MapComplete-flyer.back.svg"],
|
||||
description: Translations.t.flyer.description
|
||||
pages: [
|
||||
"/assets/templates/MapComplete-flyer.svg",
|
||||
"/assets/templates/MapComplete-flyer.back.svg",
|
||||
],
|
||||
description: Translations.t.flyer.description,
|
||||
},
|
||||
poster_a3: {
|
||||
pages: ["/assets/templates/MapComplete-poster-a3.svg"],
|
||||
description: "A basic A3 poster (similar to the flyer)"
|
||||
description: "A basic A3 poster (similar to the flyer)",
|
||||
},
|
||||
poster_a2: {
|
||||
pages: ["/assets/templates/MapComplete-poster-a2.svg"],
|
||||
description: "A basic A2 poster (similar to the flyer); scaled up from the A3 poster"
|
||||
}
|
||||
description: "A basic A2 poster (similar to the flyer); scaled up from the A3 poster",
|
||||
},
|
||||
}
|
||||
private readonly _title: string;
|
||||
private readonly _title: string
|
||||
|
||||
private readonly _pages: SvgToPdfPage[]
|
||||
|
||||
constructor(title: string, pages: string[], options?: SvgToPdfOptions) {
|
||||
this._title = title;
|
||||
this._title = title
|
||||
options = options ?? <SvgToPdfOptions>{}
|
||||
options.textSubstitutions = options.textSubstitutions ?? {}
|
||||
const mapCount = "" + Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(th => !th.hideFromOverview).length;
|
||||
const mapCount =
|
||||
"" +
|
||||
Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(
|
||||
(th) => !th.hideFromOverview
|
||||
).length
|
||||
options.textSubstitutions["mapCount"] = mapCount
|
||||
|
||||
this._pages = pages.map(page => new SvgToPdfPage(page, options))
|
||||
this._pages = pages.map((page) => new SvgToPdfPage(page, options))
|
||||
}
|
||||
|
||||
|
||||
public async ConvertSvg(language: string): Promise<void> {
|
||||
const firstPage = this._pages[0]._svgRoot
|
||||
const width = SvgToPdfInternals.attrNumber(firstPage, "width")
|
||||
|
@ -880,7 +942,7 @@ export class SvgToPdf {
|
|||
}
|
||||
|
||||
const doc = new jsPDF(mode, undefined, [width, height])
|
||||
doc.advancedAPI(advancedApi => {
|
||||
doc.advancedAPI((advancedApi) => {
|
||||
for (let i = 0; i < this._pages.length; i++) {
|
||||
if (i > 0) {
|
||||
const page = this._pages[i]._svgRoot
|
||||
|
@ -888,24 +950,31 @@ export class SvgToPdf {
|
|||
const height = SvgToPdfInternals.attrNumber(page, "height")
|
||||
|
||||
advancedApi.addPage([width, height])
|
||||
const mediabox: { bottomLeftX: number, bottomLeftY: number, topRightX: number, topRightY: number } = advancedApi.getCurrentPageInfo().pageContext.mediaBox
|
||||
const mediabox: {
|
||||
bottomLeftX: number
|
||||
bottomLeftY: number
|
||||
topRightX: number
|
||||
topRightY: number
|
||||
} = advancedApi.getCurrentPageInfo().pageContext.mediaBox
|
||||
const targetWidth = 297
|
||||
const targetHeight = 210
|
||||
const sx = mediabox.topRightX / targetWidth
|
||||
const sy = mediabox.topRightY / targetHeight
|
||||
advancedApi.setCurrentTransformationMatrix(advancedApi.Matrix(sx, 0, 0, -sy, 0, mediabox.topRightY))
|
||||
advancedApi.setCurrentTransformationMatrix(
|
||||
advancedApi.Matrix(sx, 0, 0, -sy, 0, mediabox.topRightY)
|
||||
)
|
||||
}
|
||||
this._pages[i].drawPage(advancedApi, i, language)
|
||||
}
|
||||
})
|
||||
await doc.save(this._title + "." + language + ".pdf");
|
||||
await doc.save(this._title + "." + language + ".pdf")
|
||||
}
|
||||
|
||||
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()
|
||||
translations.forEach(t => allTranslations.add(t))
|
||||
translations.forEach((t) => allTranslations.add(t))
|
||||
}
|
||||
allTranslations.delete("import")
|
||||
allTranslations.delete("version")
|
||||
|
@ -927,7 +996,9 @@ export class SvgToPdf {
|
|||
for (const page of this._pages) {
|
||||
// Load all languages at once.
|
||||
// We don't parallelize the pages, as they'll probably reload the same languages anyway (and they are cached)
|
||||
await Promise.all(languages.map(async language => await page.PrepareLanguage(language)))
|
||||
await Promise.all(
|
||||
languages.map(async (language) => await page.PrepareLanguage(language))
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -935,10 +1006,10 @@ export class SvgToPdf {
|
|||
getTranslation(translationKey: string, language: string, strict: boolean = false) {
|
||||
for (const page of this._pages) {
|
||||
const tr = page.extractTranslation(translationKey, language, strict)
|
||||
if(tr === undefined){
|
||||
if (tr === undefined) {
|
||||
continue
|
||||
}
|
||||
if(tr === translationKey){
|
||||
if (tr === translationKey) {
|
||||
continue
|
||||
}
|
||||
return tr
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue