diff --git a/Utils/svgToPdf.ts b/Utils/svgToPdf.ts index 8e260e12c..843acdda6 100644 --- a/Utils/svgToPdf.ts +++ b/Utils/svgToPdf.ts @@ -1,17 +1,16 @@ import jsPDF, {Matrix} from "jspdf"; -import Translations from "../UI/i18n/Translations"; import {Translation, TypedTranslation} from "../UI/i18n/Translation"; import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; import {PngMapCreator} from "./pngMapCreator"; import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; -import {Store, UIEventSource} from "../Logic/UIEventSource"; +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 {parseSVG, makeAbsolute} from 'svg-path-parser'; -import {And} from "../Logic/Tags/And"; -import {Tag} from "../Logic/Tags/Tag"; -import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import {makeAbsolute, parseSVG} from 'svg-path-parser'; +import Translations from "../UI/i18n/Translations"; +import {Utils} from "../Utils"; +import Locale from "../UI/i18n/Locale"; class SvgToPdfInternals { private readonly doc: jsPDF; @@ -24,13 +23,17 @@ class SvgToPdfInternals { private currentMatrixInverted: Matrix; private readonly _images: Record; + private readonly _layerTranslations: Record>; private readonly _rects: Record; + private readonly _importedTranslations: Record; - constructor(advancedApi: jsPDF, textSubstitutions: Record, images: Record, rects: Record) { + constructor(advancedApi: jsPDF, textSubstitutions: Record, images: Record, rects: Record, importedTranslations: Record, layerTranslations: Record>) { + this._layerTranslations = layerTranslations; this.textSubstitutions = textSubstitutions; this.doc = advancedApi; this._images = images; this._rects = rects; + this._importedTranslations = importedTranslations; this.currentMatrix = this.doc.unitMatrix; this.currentMatrixInverted = this.doc.unitMatrix; } @@ -119,26 +122,46 @@ class SvgToPdfInternals { return r }; - private drawRect(element: Element) { + 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 style = element.getAttribute("style") - - const css = SvgToPdfInternals.parseCss(style) - if (css["fill-opacity"] !== "0") { + const ry = SvgToPdfInternals.attrNumber(element, "ry", false) ?? 0 + const rx = SvgToPdfInternals.attrNumber(element, "rx", false) ?? 0 + const css = SvgToPdfInternals.css(element) + if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { this.doc.setFillColor(css["fill"] ?? "black") - this.doc.rect(x, y, width, height, "F") + this.doc.roundedRect(x, y, width, height, rx, ry, "F") } - if (css["stroke"]) { + if (css["stroke"] && css["stroke"] !== "none") { this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) this.doc.setDrawColor(css["stroke"] ?? "black") - this.doc.rect(x, y, width, height, "S") + this.doc.roundedRect(x, y, width, height, rx, ry, "S") } 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 + } + + private static attr(element: Element, name: string, recurseup: boolean = true): string | undefined { if (element === null || element === undefined) { return undefined @@ -187,20 +210,35 @@ class SvgToPdfInternals { } private extractTranslation(text: string) { - const pathPart = text.match(/\$(([_a-zA-Z0-9]+\.)+[_a-zA-Z0-9]+)(.*)/) + const pathPart = text.match(/\$(([_a-zA-Z0-9?]+\.)+[_a-zA-Z0-9?]+)(.*)/) if (pathPart === null) { return text } - const path = pathPart[1].split(".") - const rest = pathPart[3] ?? "" 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[Locale.language.data] + if (t === undefined) { + console.error("No layerTranslation available for language " + Locale.language.data) + 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) + console.error("No value found to substitute " + text, "the path is", path) return undefined } } + + if (typeof t === "string") { + t = new TypedTranslation({"*": t}) + } if (t instanceof TypedTranslation) { return (>t).Subs(this.textSubstitutions).txt + rest } else { @@ -324,6 +362,8 @@ class SvgToPdfInternals { } private drawPath(element: SVGPathElement): void { + if (element.id === "rect25327") + console.log("Drawing", element.id, element) const path = element.getAttribute("d") const parsed: { code: string, x: number, y: number, x2?, y2?, x1?, y1? }[] = parseSVG(path) makeAbsolute(parsed) @@ -335,57 +375,92 @@ class SvgToPdfInternals { continue } + if (c.code === "H") { + 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]} + this.doc.path([command]) + continue + } + 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" const css = SvgToPdfInternals.css(element) - this.doc.setDrawColor(css["color"]) - this.doc.setFillColor(css["fill"]) + if (css["color"] && css["color"].toLowerCase() !== "none") { + this.doc.setDrawColor(css["color"]) + } if (css["stroke-width"]) { - this.doc.setLineWidth(Number(css["stroke-width"])) + this.doc.setLineWidth(parseFloat(css["stroke-width"])) } if (css["stroke-linejoin"] !== undefined) { this.doc.setLineJoin(css["stroke-linejoin"]) } + let doFill = false if (css["fill-rule"] === "evenodd") { this.doc.fillEvenOdd() - } else { + } 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) { this.doc.fill() } } public handleElement(element: SVGSVGElement | Element): void { const isTransformed = this.setTransform(element) - if (element.tagName === "tspan") { - if (element.childElementCount == 0) { - this.drawTspan(element) - } else { + 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(element) + } + + if (element.tagName === "g" || element.tagName === "text") { + for (let child of Array.from(element.children)) { this.handleElement(child) } } - } - if (element.tagName === "image") { - this.drawImage(element) - } - - if (element.tagName === "path") { - this.drawPath(element) - } - - if (element.tagName === "g" || element.tagName === "text") { - - for (let child of Array.from(element.children)) { - this.handleElement(child) + if (element.tagName === "rect") { + this.drawRect(element) } - } - if (element.tagName === "rect") { - this.drawRect(element) - } + if (element.tagName === "circle") { + this.drawCircle(element) + } + } catch (e) { + console.error("Could not handle element", element, "due to", e) + } if (isTransformed) { this.undoTransform() } @@ -418,35 +493,27 @@ class SvgToPdfInternals { export interface SvgToPdfOptions { getFreeDiv: () => string, disableMaps?: false | true - textSubstitutions?: Record, beforePage?: (i: number) => void - + textSubstitutions?: Record, + beforePage?: (i: number) => void, } -export class SvgToPdf { + +export class SvgToPdfPage { private images: Record = {} private rects: Record = {} - private readonly _svgRoots: SVGSVGElement[] = []; - private readonly _textSubstitutions: Record; - private readonly _beforePage: ((i: number) => void) | undefined; + public readonly _svgRoot: SVGSVGElement; public readonly _usedTranslations: Set = new Set() - private readonly _freeDivId: () => string; - private readonly _currentState = new UIEventSource("Initing") public readonly currentState: Store - private readonly _disableMaps: boolean ; + private readonly importedTranslations: Record = {} + private readonly layerTranslations: Record> = {} + private readonly options: SvgToPdfOptions - constructor(pages: string[], options?:SvgToPdfOptions) { - this.currentState = this._currentState - this._textSubstitutions = options?.textSubstitutions ?? {}; - this._beforePage = options?.beforePage; - this._freeDivId = options?.getFreeDiv - this._disableMaps = options.disableMaps ?? false + constructor(page: string, options?: SvgToPdfOptions) { + this.options = options ?? ({}) const parser = new DOMParser(); - for (const page of pages) { - const xmlDoc = parser.parseFromString(page, "image/svg+xml"); - const svgRoot = xmlDoc.getElementsByTagName("svg")[0]; - this._svgRoots.push(svgRoot) - } + const xmlDoc = parser.parseFromString(page, "image/svg+xml"); + this._svgRoot = xmlDoc.getElementsByTagName("svg")[0]; } @@ -476,7 +543,6 @@ export class SvgToPdf { } this.images[xlink] = img - this.setState("Preparing: loading image " + Object.keys(this.images).length + ": " + img.src.substring(0, 30)) return new Promise((resolve) => { img.onload = _ => { resolve() @@ -502,6 +568,17 @@ export class SvgToPdf { if (translationMatch !== null) { this._usedTranslations.add(translationMatch[1]) } + 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.?:]+),(.+)\)/) + if (setPropertyMatch) { + 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(element) @@ -520,10 +597,6 @@ export class SvgToPdf { private _isPrepared = false; - private setState(message: string) { - this._currentState.setData(message) - } - private async prepareMap(mapSpec: SVGTSpanElement,): Promise { // Upper left point of the tspan const {x, y} = SvgToPdfInternals.GetActualXY(mapSpec) @@ -540,7 +613,6 @@ export class SvgToPdf { } const params = SvgToPdfInternals.parseCss(match[1], ",") const ctx = `Preparing map (theme ${params["theme"]})` - this.setState(ctx + "...") let smallestRect: SVGRectElement = undefined let smallestSurface: number = undefined; @@ -589,7 +661,7 @@ export class SvgToPdf { const key = params[paramsKey].toLowerCase().trim() const layer = layout.layers.find(l => l.id === layerName) if (key === "force") { - console.log("Forcing minzoom of layer",layer.id) + console.log("Forcing minzoom of layer", layer.id) layer.minzoom = 0 layer.minzoomVisible = 0 } @@ -619,7 +691,7 @@ export class SvgToPdf { 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,")") + console.log("Setting ", layer?.layerDef?.id, " to visibility", isDisplayed, "(minzoom:", layer?.layerDef?.minzoomVisible, layer?.layerDef?.minzoom, ")") layer.isDisplayed.setData( isDisplayed ) @@ -630,18 +702,16 @@ export class SvgToPdf { } } - this.setState(ctx + ": loading map data...") const pngCreator = new PngMapCreator( state, { width, height, scaling: Number(params["scaling"] ?? 1.5), - divId: this._freeDivId(), - dummyMode : this._disableMaps + divId: this.options.getFreeDiv(), + dummyMode: this.options.disableMaps } ) - this.setState(ctx + ": rendering png") const png = await pngCreator.CreatePng("image") svgImage.setAttribute('xlink:href', png) @@ -657,17 +727,19 @@ export class SvgToPdf { textElement.parentElement.removeChild(textElement) } - public async Prepare() { + public async Prepare(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]["shared-questions"] = shared_questions["shared_questions"] + if (this._isPrepared) { return } this._isPrepared = true; - this.setState("Preparing...") const mapSpecs: SVGTSpanElement[] = [] - for (const svgRoot of this._svgRoots) { - for (let child of Array.from(svgRoot.children)) { - await this.prepareElement(child, mapSpecs) - } + for (let child of Array.from(this._svgRoot.children)) { + await this.prepareElement(child, mapSpecs) } const self = this; @@ -676,36 +748,58 @@ export class SvgToPdf { } - public async ConvertSvg(saveAs: string): Promise { - await this.Prepare() - const ctx = "Rendering PDF" - this.setState(ctx + "...") - const firstPage = this._svgRoots[0] + public drawPage(advancedApi: jsPDF, i: number, language: string): void { + if(!this._isPrepared){ + throw "Run 'Prepare()' first!" + } + + if (this.options.beforePage) { + this.options.beforePage(i) + } + const internal = new SvgToPdfInternals(advancedApi, this.options.textSubstitutions, this.images, this.rects, this.importedTranslations, this.layerTranslations); + for (let child of Array.from(this._svgRoot.children)) { + internal.handleElement(child) + } + } + +} + +export class SvgToPdf { + + private readonly _pages: SvgToPdfPage[] + + constructor(pages: string[], options?: SvgToPdfOptions) { + this._pages = pages.map(page => new SvgToPdfPage(page, options)) + } + + + public async ConvertSvg(saveAs: string, language: string): Promise { + const firstPage = this._pages[0]._svgRoot const width = SvgToPdfInternals.attrNumber(firstPage, "width") const height = SvgToPdfInternals.attrNumber(firstPage, "height") const mode = width > height ? "landscape" : "portrait" + for (const page of this._pages) { + await page.Prepare(language) + } + + Locale.language.setData(language) const doc = new jsPDF(mode) - const beforePage = this._beforePage ?? (_ => { - }); - const svgRoots = this._svgRoots; doc.advancedAPI(advancedApi => { - const internal = new SvgToPdfInternals(advancedApi, this._textSubstitutions, this.images, this.rects); - for (let i = 0; i < this._svgRoots.length; i++) { - this.setState(ctx + ": page " + i + "/" + this._svgRoots.length) - beforePage(i) - const svgRoot = svgRoots[i]; - for (let child of Array.from(svgRoot.children)) { - internal.handleElement(child) - } + for (let i = 0; i < this._pages.length; i++) { if (i > 0) { advancedApi.addPage() + 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)) } + this._pages[i].drawPage(advancedApi, i, language) } }) - this.setState("Serving PDF...") await doc.save(saveAs); - this.setState("Done") } diff --git a/assets/templates/MapComplete-flyer.back.svg b/assets/templates/MapComplete-flyer.back.svg index 70f046a9a..1b8cc55d4 100644 --- a/assets/templates/MapComplete-flyer.back.svg +++ b/assets/templates/MapComplete-flyer.back.svg @@ -26,9 +26,9 @@ showgrid="false" showguides="true" inkscape:guide-bbox="true" - inkscape:zoom="1.0169528" - inkscape:cx="389.89027" - inkscape:cy="403.16522" + inkscape:zoom="1.0430996" + inkscape:cx="836.4494" + inkscape:cy="155.30636" inkscape:window-width="1920" inkscape:window-height="1007" inkscape:window-x="0" @@ -347,8 +347,8 @@ + $map(theme:aed,z:14,lat:51.2098,lon:3.2284) + id="tspan8484">$map(theme:aed,z:14,lat:51.2098,lon:3.2284) $flyer.toerisme_vlaanderen + id="tspan8492">$flyer.toerisme_vlaanderen $map(theme:toerisme_vlaanderen,layers:none$map(theme:toerisme_vlaanderen,layers:none,layer-charging_station_ebikes:force,lat:,layer-charging_station_ebikes:force,lat:51.02403,lon:5.1, z:10) + id="tspan8504">51.02403,lon:5.1, z:10) $map(theme:cyclofix,z:14,lat:$map(theme:cyclofix,z:14,lat:51.05016,lon:51.05016,lon:3.717842,layers:none,layer-3.717842,layers:none,layer-bike_repair_station:true,layer-bike_repair_station:true,layer-drinking_water:true,layer-bike_cafe:true,layer-drinking_water:true,layer-bike_cafe:true,layer-bicycle_tube_vending_machine: true) + id="tspan8526">bicycle_tube_vending_machine: true) $map(theme:artwork,z:15,lat:51.2098,lon:$map(theme:artwork,z:15,lat:51.2098,lon:3.2284,background:AGIV) + id="tspan8534">3.2284,background:AGIV) $map(theme:cyclestreets,z:12,lat:51.2098,lon:$map(theme:cyclestreets,z:12,lat:51.2098,lon:3.2284) + id="tspan8542">3.2284) @@ -585,14 +596,14 @@ style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect890);display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264848;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">$map(theme:benches,z:14,lat:51.2098,lon:$map(theme:benches,z:14,lat:51.2098,lon:3.2284, layers:none, layer-bench:force) + id="tspan8550">3.2284, layers:none, layer-bench:force) $flyer.aerial + id="tspan8554">$flyer.aerial $flyer.examples + id="tspan8558">$flyer.examples @@ -645,6 +656,44 @@ id="image24447" x="272.42087" y="32.116085" /> + + + + + + + + + + $flyer.lines_too $flyer.title + id="tspan8566">$flyer.title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $flyer.frontParagraph + id="tspan2963">$flyer.frontParagraph $flyer.tagline + id="tspan2967">$flyer.tagline $flyer.title + id="tspan2971">$flyer.title $flyer.whatIsOsm + id="tspan2973">$flyer.whatIsOsm $flyer.mapcomplete.title + id="tspan2977">$flyer.mapcomplete.title $flyer.mapcomplete.intro + id="tspan2981">$flyer.mapcomplete.intro + id="tspan2985"> $list(flyer.mapcomplete.li) + id="tspan2989">$list(flyer.mapcomplete.li) + id="tspan2993"> + id="tspan2997"> + id="tspan3001"> + id="tspan3005"> + id="tspan3009"> + id="tspan3013"> + id="tspan3017"> $flyer.mapcomplete.customize + id="tspan3021">$flyer.mapcomplete.customize $flyer.callToAction$flyer.callToAction + id="tspan3027"> - + id="tspan3031"> $flyer.osm + id="tspan3035">$flyer.osm + $flyer.editing.title + $flyer.editing.intro + + + + + + $nr.title.render + $import layer.nature_reserve as nr +$import layer.nature_reserve.tagRenderings.Dogs? as nrd +$import layer.nature_reserve.tagRenderings.Access tag as nra +$import layer.nature_reserve.tagRenderings.Surface area as nrsa +$import flyer.fakeui as fui +$import general as g +$set(_surface:ha, 13.2) + + + + + + + + + + + + + + $image.addPicture + + Pieter Vander Vennet + $fui.add_images + + + + + + + + + + $fui.see_images + + + + $fui.wikipedia + CC0 + $nra.mappings.0.then + + + $nrsa.render + $fui.attributes + + + + + + + + + + + $fui.edit + + www.example.org/nature + + + + + + + + + + + + + + + + + $g.wikipedia.fromWikipedia + + + + + + + + + + + + + + + + + $fui.question + + + + + + + + + + + + + + + + + + + + + + + $general.save + + $nrd.question + $nrd.mappings.0.then +$nrd.mappings.1.then +$nrd.mappings.2.then + Skip + + + diff --git a/langs/en.json b/langs/en.json index 1794716d7..427da9164 100644 --- a/langs/en.json +++ b/langs/en.json @@ -42,8 +42,21 @@ "flyer": { "aerial": "This map uses a different background, namely aerial imagery by Agentschap Informatie Vlaanderen", "callToAction": "Test it on mapcomplete.osm.be", + "editing": { + "intro": "The user is greeted by a map with points. Upon selecting a point, the information about the point is shown. A simplified example of what this looks like for a nature reserve is shown below.", + "title": "What does the interface look like?" + }, "examples": "There are many thematic maps available of which a few are printed here, namely the maps with AED's, artwork, cyclestreets, benches and cyclepumps.\n\nThere are many more thematic maps online: about healthcare, indoor navigation, wheelchair accessibility, waste facilities, public bookcases, pedestrian crossings with a rainbow-painting,... Discover them all on mapcomplete.osm.be ", + "fakeui": { + "add_images": "Add images with a few clicks", + "attributes": "Shows attributes in a friendly way", + "edit": "Wrong or outdated info? The edit button is right there", + "question": "If an attribute is not yet known, MapComplete shows a question", + "see_images": "Shows images from previous contributors, Wikipedia, Mapillary, ... ", + "wikipedia": "Linked Wikipedia articles are shown" + }, "frontParagraph": "MapComplete is an easy to use web application to collect geodata in OpenStreetMap, enabling collecting and managing relevant data in an open, crowdsourced and reusable way.\n\nNew categories and attributes can be added upon request.", + "lines_too": "Lines and polygons are shown too. Attributes and images can be added and updated as well.", "mapcomplete": { "customize": "MapComplete can tailored to your needs, with new map layers, new functionalities or styled to your organisation styleguide. We also have experience with starting campaigns to crowdsource geodata.\nContact pietervdvn@posteo.net for a quote.", "intro": "MapComplete is a website which has {mapCount} interactive maps. Every single map allows to add or update information.", diff --git a/langs/nl.json b/langs/nl.json index 9933e142f..a6d1b3dfd 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -42,7 +42,19 @@ "flyer": { "aerial": "Deze kaart gebruikt luchtfoto's van het Agentschap Informatie Vlaanderen als achtergrond.\nOok het GRB is beschikbaar als achtergrondlaag.", "callToAction": "Probeer het uit op mapcomplete.osm.be", + "editing": { + "intro": "De gebruiker krijgt eerst een kaart met interesspunten te zien. Wanneer er op een punt geklikt wordt, wordt een interface met informatie geopend. Een (versimpeld) voorbeeld voor een natuurgebied wordt hieronder getoond:", + "title": "Hoe ziet de interface eruit?" + }, "examples": "Er zijn vele thematische kaarten beschikbaar. Enkele voorbeelden zijn hier geprint, zoals de kaart met AEDs, kunstwerken, fietsstraten, banken en fietspompen.\n\nOnline zijn er nog kaarten met diverse thema's, zoals gezondheidszorg, binnenruimtes, rolstoeltoegankelijkheid, afvalcontainers, boekenruilkasten, regenboog-zebrapaden,... Ontdek ze allemaal mapcomplete.osm.be ", + "fakeui": { + "add_images": "Voeg foto's met een paar klikken toe", + "attributes": "Attributen worden getoond op begrijpbare wijze", + "edit": "Foute of verouderde gegevens? Aanpassen kan hier", + "question": "Is een attribuut nog niet gekend? MapComplete toont een vraag", + "see_images": "Toont afbeeldingen van eerdere bijdragers, Wikipedia, Mapillary, ...", + "wikipedia": "Gelinkte Wikipedia-artikels worden getoond" + }, "frontParagraph": "MapComplete is een web-applicatie om OpenStreetMap-data te tonen en aan te passen op basis van thematische kaarten. Het maakt het mogelijk om open geodata te crowdsourcen en te managen op een makkelijke manier.\n\nNieuwe categorie�n en attributen kunnen op vraag worden toegevoegd.", "mapcomplete": { "customize": "Wil je een versie op maat? Wil je een versie in jullie huisstijl?\nWil je een nieuwe kaartlaag of functionaliteit? Wil je een crowdsourcing-campagne opzetten?\nNeem contact op met pietervdvn@posteo.net voor een offerte.", diff --git a/test.ts b/test.ts index ca6c013ce..bd8b29eb3 100644 --- a/test.ts +++ b/test.ts @@ -41,47 +41,12 @@ async function main() { textSubstitutions: { mapCount: "" + Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(th => !th.hideFromOverview).length }, - disableMaps: false + disableMaps: true } - Locale.language.setData("nl") - const back = new SvgToPdf([svgBack], options) - const front = await new SvgToPdf([svg], options) - await back.ConvertSvg("Flyer-back-nl.pdf") - await front.ConvertSvg("Flyer-front-nl.pdf") - Locale.language.setData("en") - await back.ConvertSvg("Flyer-back-en.pdf") - await front.ConvertSvg("Flyer-front-en.pdf") + const front = await new SvgToPdf([svg, svgBack], options) + await front.ConvertSvg("Flyer-nl.pdf", "nl") + await front.ConvertSvg("Flyer-en.pdf", "en") - - /* - const svg = await Utils.download(window.location.protocol + "//" + window.location.host + "/assets/templates/MapComplete-flyer.svg") - - - Locale.language.setData("en") - const svgToPdf = new SvgToPdf([svgBack], { - getFreeDiv: createElement, - textSubstitutions: { - mapCount: "" + Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(th => !th.hideFromOverview).length - } - }) - new VariableUiElement(svgToPdf.currentState).AttachTo("maindiv") - await svgToPdf.Prepare() - console.log("Used translations", svgToPdf._usedTranslations) - await svgToPdf.ConvertSvg("flyer_nl.pdf") - /* -Locale.language.setData("en") -await new SvgToPdf([svgBack], { - textSubstitutions: { - mapCount: "" + Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(th => !th.hideFromOverview).length - } -}).ConvertSvg("flyer_en.pdf") - -Locale.language.setData("nl") -await new SvgToPdf([svgBack], { - textSubstitutions: { - mapCount: "" + Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(th => !th.hideFromOverview).length - } -}).ConvertSvg("flyer_nl.pdf")*/ } main().then(() => console.log("Done!"))