mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-23 23:57:23 +01:00
Merge branch 'master' into feature/split-label-view-data
This commit is contained in:
commit
ba0ce8e40b
47 changed files with 8023 additions and 5800 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,393 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const fonts = [
|
||||
{family: "Arial"},
|
||||
{family: "Brush Script MT"},
|
||||
{family: "Century Gothic"},
|
||||
{family: "Comic Sans MS"},
|
||||
{family: "Copperplate"},
|
||||
{family: "Courier New"},
|
||||
{family: "Garamond"},
|
||||
{family: "Georgia"},
|
||||
{family: "Herculanum"},
|
||||
{family: "Impact"},
|
||||
{family: "Papyrus"},
|
||||
{family: "Party LET"},
|
||||
{family: "Times New Roman"},
|
||||
{family: "Verdana"},
|
||||
{
|
||||
family: "Almendra SC",
|
||||
src: "url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Amarante",
|
||||
src: "url(https://fonts.gstatic.com/s/amarante/v22/xMQXuF1KTa6EvGx9bp-wAXs.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Amatic SC",
|
||||
src: "url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Arima Madurai",
|
||||
src: "url(https://fonts.gstatic.com/s/arimamadurai/v14/t5tmIRoeKYORG0WNMgnC3seB3T7Prw.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Architects Daughter",
|
||||
src: "url(https://fonts.gstatic.com/s/architectsdaughter/v8/RXTgOOQ9AAtaVOHxx0IUBM3t7GjCYufj5TXV5VnA2p8.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Bitter",
|
||||
src: "url(https://fonts.gstatic.com/s/bitter/v12/zfs6I-5mjWQ3nxqccMoL2A.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Caesar Dressing",
|
||||
src: "url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Cinzel",
|
||||
src: "url(https://fonts.gstatic.com/s/cinzel/v7/zOdksD_UUTk1LJF9z4tURA.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Dancing Script",
|
||||
src: "url(https://fonts.gstatic.com/s/dancingscript/v9/KGBfwabt0ZRLA5W1ywjowUHdOuSHeh0r6jGTOGdAKHA.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Eagle Lake",
|
||||
src: "url(https://fonts.gstatic.com/s/eaglelake/v24/ptRMTiqbbuNJDOiKj9wG1On4KCFtpe4.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Faster One",
|
||||
src: "url(https://fonts.gstatic.com/s/fasterone/v17/H4ciBXCHmdfClFb-vWhf-LyYhw.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Forum",
|
||||
src: "url(https://fonts.gstatic.com/s/forum/v16/6aey4Ky-Vb8Ew8IROpI.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Fredericka the Great",
|
||||
src: "url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Gloria Hallelujah",
|
||||
src: "url(https://fonts.gstatic.com/s/gloriahallelujah/v9/CA1k7SlXcY5kvI81M_R28cNDay8z-hHR7F16xrcXsJw.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Great Vibes",
|
||||
src: "url(https://fonts.gstatic.com/s/greatvibes/v5/6q1c0ofG6NKsEhAc2eh-3Y4P5ICox8Kq3LLUNMylGO4.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Henny Penny",
|
||||
src: "url(https://fonts.gstatic.com/s/hennypenny/v17/wXKvE3UZookzsxz_kjGSfPQtvXI.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "IM Fell English",
|
||||
src: "url(https://fonts.gstatic.com/s/imfellenglish/v7/xwIisCqGFi8pff-oa9uSVAkYLEKE0CJQa8tfZYc_plY.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Kelly Slab",
|
||||
src: "url(https://fonts.gstatic.com/s/kellyslab/v15/-W_7XJX0Rz3cxUnJC5t6fkQLfg.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Kranky",
|
||||
src: "url(https://fonts.gstatic.com/s/kranky/v24/hESw6XVgJzlPsFn8oR2F.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Lobster Two",
|
||||
src: "url(https://fonts.gstatic.com/s/lobstertwo/v18/BngMUXZGTXPUvIoyV6yN5-fN5qU.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Lugrasimo",
|
||||
src: "url(https://fonts.gstatic.com/s/lugrasimo/v4/qkBXXvoF_s_eT9c7Y7au455KsgbLMA.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Kaushan Script",
|
||||
src: "url(https://fonts.gstatic.com/s/kaushanscript/v6/qx1LSqts-NtiKcLw4N03IEd0sm1ffa_JvZxsF_BEwQk.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Macondo",
|
||||
src: "url(https://fonts.gstatic.com/s/macondo/v21/RrQQboN9-iB1IXmOe2LE0Q.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "MedievalSharp",
|
||||
src: "url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Metal Mania",
|
||||
src: "url(https://fonts.gstatic.com/s/metalmania/v22/RWmMoKWb4e8kqMfBUdPFJdXFiaQ.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Metamorphous",
|
||||
src: "url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Montez",
|
||||
src: "url(https://fonts.gstatic.com/s/montez/v8/aq8el3-0osHIcFK6bXAPkw.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Nova Script",
|
||||
src: "url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Orbitron",
|
||||
src: "url(https://fonts.gstatic.com/s/orbitron/v9/HmnHiRzvcnQr8CjBje6GQvesZW2xOQ-xsNqO47m55DA.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Oregano",
|
||||
src: "url(https://fonts.gstatic.com/s/oregano/v13/If2IXTPxciS3H4S2oZDVPg.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Pirata One",
|
||||
src: "url(https://fonts.gstatic.com/s/pirataone/v22/I_urMpiDvgLdLh0fAtofhi-Org.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Sail",
|
||||
src: "url(https://fonts.gstatic.com/s/sail/v16/DPEjYwiBxwYJJBPJAQ.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Satisfy",
|
||||
src: "url(https://fonts.gstatic.com/s/satisfy/v8/2OzALGYfHwQjkPYWELy-cw.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Shadows Into Light",
|
||||
src: "url(https://fonts.gstatic.com/s/shadowsintolight/v7/clhLqOv7MXn459PTh0gXYFK2TSYBz0eNcHnp4YqE4Ts.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
},
|
||||
{
|
||||
family: "Tapestry",
|
||||
src: "url(https://fonts.gstatic.com/s/macondo/v21/RrQQboN9-iB1IXmOe2LE0Q.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Uncial Antiqua",
|
||||
src: "url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Underdog",
|
||||
src: "url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "UnifrakturMaguntia",
|
||||
src: "url(https://fonts.gstatic.com/s/unifrakturmaguntia/v16/WWXPlieVYwiGNomYU-ciRLRvEmK7oaVemGZM.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
|
||||
},
|
||||
{
|
||||
family: "Yellowtail",
|
||||
src: "url(https://fonts.gstatic.com/s/yellowtail/v8/GcIHC9QEwVkrA19LJU1qlPk_vArhqVIZ0nv9q090hN8.woff2)",
|
||||
unicodeRange:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
|
||||
}
|
||||
];
|
||||
|
||||
declareDefaultFonts(); // execute once on load
|
||||
|
||||
function declareFont(font) {
|
||||
const {family, src, ...rest} = font;
|
||||
addFontOption(family);
|
||||
|
||||
if (!src) return;
|
||||
const fontFace = new FontFace(family, src, {...rest, display: "block"});
|
||||
document.fonts.add(fontFace);
|
||||
}
|
||||
|
||||
function declareDefaultFonts() {
|
||||
fonts.forEach(font => declareFont(font));
|
||||
}
|
||||
|
||||
function getUsedFonts(svg) {
|
||||
const usedFontFamilies = new Set();
|
||||
|
||||
const labelGroups = svg.querySelectorAll("#labels g");
|
||||
for (const labelGroup of labelGroups) {
|
||||
const font = labelGroup.getAttribute("font-family");
|
||||
if (font) usedFontFamilies.add(font);
|
||||
}
|
||||
|
||||
const provinceFont = provs.attr("font-family");
|
||||
if (provinceFont) usedFontFamilies.add(provinceFont);
|
||||
|
||||
const legend = svg.querySelector("#legend");
|
||||
const legendFont = legend?.getAttribute("font-family");
|
||||
if (legendFont) usedFontFamilies.add(legendFont);
|
||||
|
||||
const usedFonts = fonts.filter(font => usedFontFamilies.has(font.family));
|
||||
return usedFonts;
|
||||
}
|
||||
|
||||
function addFontOption(family) {
|
||||
const options = document.getElementById("styleSelectFont");
|
||||
const option = document.createElement("option");
|
||||
option.value = family;
|
||||
option.innerText = family;
|
||||
option.style.fontFamily = family;
|
||||
options.add(option);
|
||||
}
|
||||
|
||||
async function fetchGoogleFont(family) {
|
||||
const url = `https://fonts.googleapis.com/css2?family=${family.replace(/ /g, "+")}`;
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
const text = await resp.text();
|
||||
|
||||
const fontFaceRules = text.match(/font-face\s*{[^}]+}/g);
|
||||
const fonts = fontFaceRules.map(fontFace => {
|
||||
const srcURL = fontFace.match(/url\(['"]?(.+?)['"]?\)/)[1];
|
||||
const src = `url(${srcURL})`;
|
||||
const unicodeRange = fontFace.match(/unicode-range: (.*?);/)?.[1];
|
||||
const variant = fontFace.match(/font-style: (.*?);/)?.[1];
|
||||
|
||||
const font = {family, src};
|
||||
if (unicodeRange) font.unicodeRange = unicodeRange;
|
||||
if (variant && variant !== "normal") font.variant = variant;
|
||||
return font;
|
||||
});
|
||||
|
||||
return fonts;
|
||||
} catch (err) {
|
||||
ERROR && console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readBlobAsDataURL(blob) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFontsAsDataURI(fonts) {
|
||||
const promises = fonts.map(async font => {
|
||||
const url = font.src.match(/url\(['"]?(.+?)['"]?\)/)[1];
|
||||
const resp = await fetch(url);
|
||||
const blob = await resp.blob();
|
||||
const dataURL = await readBlobAsDataURL(blob);
|
||||
|
||||
return {...font, src: `url('${dataURL}')`};
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
async function addGoogleFont(family) {
|
||||
const fontRanges = await fetchGoogleFont(family);
|
||||
if (!fontRanges) return tip("Cannot fetch Google font for this value", true, "error", 4000);
|
||||
tip(`Google font ${family} is loading...`, true, "warn", 4000);
|
||||
|
||||
const promises = fontRanges.map(range => {
|
||||
const {src, unicodeRange, variant} = range;
|
||||
const fontFace = new FontFace(family, src, {unicodeRange, variant, display: "block"});
|
||||
return fontFace.load();
|
||||
});
|
||||
|
||||
Promise.all(promises)
|
||||
.then(fontFaces => {
|
||||
fontFaces.forEach(fontFace => document.fonts.add(fontFace));
|
||||
fonts.push(...fontRanges);
|
||||
tip(`Google font ${family} is added to the list`, true, "success", 4000);
|
||||
addFontOption(family);
|
||||
document.getElementById("styleSelectFont").value = family;
|
||||
changeFont();
|
||||
})
|
||||
.catch(err => {
|
||||
tip(`Failed to load Google font ${family}`, true, "error", 4000);
|
||||
ERROR && console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
function addLocalFont(family) {
|
||||
fonts.push({family});
|
||||
|
||||
const fontFace = new FontFace(family, `local(${family})`, {display: "block"});
|
||||
document.fonts.add(fontFace);
|
||||
tip(`Local font ${family} is added to the fonts list`, true, "success", 4000);
|
||||
addFontOption(family);
|
||||
document.getElementById("styleSelectFont").value = family;
|
||||
changeFont();
|
||||
}
|
||||
|
||||
function addWebFont(family, url) {
|
||||
const src = `url('${url}')`;
|
||||
fonts.push({family, src});
|
||||
|
||||
const fontFace = new FontFace(family, src, {display: "block"});
|
||||
document.fonts.add(fontFace);
|
||||
tip(`Font ${family} is added to the list`, true, "success", 4000);
|
||||
addFontOption(family);
|
||||
document.getElementById("styleSelectFont").value = family;
|
||||
changeFont();
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
// Ice layer data model - separates ice data from SVG rendering
|
||||
window.Ice = (function () {
|
||||
|
||||
// Find next available id for new ice element idealy filling gaps
|
||||
function getNextId() {
|
||||
if (pack.ice.length === 0) return 0;
|
||||
// find gaps in existing ids
|
||||
const existingIds = pack.ice.map(e => e.i).sort((a, b) => a - b);
|
||||
for (let id = 0; id < existingIds[existingIds.length - 1]; id++) {
|
||||
if (!existingIds.includes(id)) return id;
|
||||
}
|
||||
return existingIds[existingIds.length - 1] + 1;
|
||||
}
|
||||
|
||||
// Generate glaciers and icebergs based on temperature and height
|
||||
function generate() {
|
||||
clear();
|
||||
const { cells, features } = grid;
|
||||
const { temp, h } = cells;
|
||||
Math.random = aleaPRNG(seed);
|
||||
|
||||
const ICEBERG_MAX_TEMP = 0;
|
||||
const GLACIER_MAX_TEMP = -8;
|
||||
const minMaxTemp = d3.min(temp);
|
||||
|
||||
// Generate glaciers on cold land
|
||||
{
|
||||
const type = "iceShield";
|
||||
const getType = cellId =>
|
||||
h[cellId] >= 20 && temp[cellId] <= GLACIER_MAX_TEMP ? type : null;
|
||||
const isolines = getIsolines(grid, getType, { polygons: true });
|
||||
|
||||
if (isolines[type]?.polygons) {
|
||||
isolines[type].polygons.forEach(points => {
|
||||
const clipped = clipPoly(points);
|
||||
pack.ice.push({
|
||||
i: getNextId(),
|
||||
points: clipped,
|
||||
type: "glacier"
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate icebergs on cold water
|
||||
for (const cellId of grid.cells.i) {
|
||||
const t = temp[cellId];
|
||||
if (h[cellId] >= 20) continue; // no icebergs on land
|
||||
if (t > ICEBERG_MAX_TEMP) continue; // too warm: no icebergs
|
||||
if (features[cells.f[cellId]].type === "lake") continue; // no icebergs on lakes
|
||||
if (P(0.8)) continue; // skip most of eligible cells
|
||||
|
||||
const randomFactor = 0.8 + rand() * 0.4; // random size factor
|
||||
let baseSize = (1 - normalize(t, minMaxTemp, 1)) * 0.8; // size: 0 = zero, 1 = full
|
||||
if (cells.t[cellId] === -1) baseSize /= 1.3; // coastline: smaller icebergs
|
||||
const size = minmax(rn(baseSize * randomFactor, 2), 0.1, 1);
|
||||
|
||||
const [cx, cy] = grid.points[cellId];
|
||||
const points = getGridPolygon(cellId).map(([x, y]) => [
|
||||
rn(lerp(cx, x, size), 2),
|
||||
rn(lerp(cy, y, size), 2)
|
||||
]);
|
||||
|
||||
pack.ice.push({
|
||||
i: getNextId(),
|
||||
points,
|
||||
type: "iceberg",
|
||||
cellId,
|
||||
size
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addIceberg(cellId, size) {
|
||||
const [cx, cy] = grid.points[cellId];
|
||||
const points = getGridPolygon(cellId).map(([x, y]) => [
|
||||
rn(lerp(cx, x, size), 2),
|
||||
rn(lerp(cy, y, size), 2)
|
||||
]);
|
||||
const id = getNextId();
|
||||
pack.ice.push({
|
||||
i: id,
|
||||
points,
|
||||
type: "iceberg",
|
||||
cellId,
|
||||
size
|
||||
});
|
||||
redrawIceberg(id);
|
||||
}
|
||||
|
||||
function removeIce(id) {
|
||||
const index = pack.ice.findIndex(element => element.i === id);
|
||||
if (index !== -1) {
|
||||
const type = pack.ice.find(element => element.i === id).type;
|
||||
pack.ice.splice(index, 1);
|
||||
if (type === "glacier") {
|
||||
redrawGlacier(id);
|
||||
} else {
|
||||
redrawIceberg(id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function updateIceberg(id, points, size) {
|
||||
const iceberg = pack.ice.find(element => element.i === id);
|
||||
if (iceberg) {
|
||||
iceberg.points = points;
|
||||
iceberg.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
function randomizeIcebergShape(id) {
|
||||
const iceberg = pack.ice.find(element => element.i === id);
|
||||
if (!iceberg) return;
|
||||
|
||||
const cellId = iceberg.cellId;
|
||||
const size = iceberg.size;
|
||||
const [cx, cy] = grid.points[cellId];
|
||||
|
||||
// Get a different random cell for the polygon template
|
||||
const i = ra(grid.cells.i);
|
||||
const cn = grid.points[i];
|
||||
const poly = getGridPolygon(i).map(p => [p[0] - cn[0], p[1] - cn[1]]);
|
||||
const points = poly.map(p => [
|
||||
rn(cx + p[0] * size, 2),
|
||||
rn(cy + p[1] * size, 2)
|
||||
]);
|
||||
|
||||
iceberg.points = points;
|
||||
}
|
||||
|
||||
function changeIcebergSize(id, newSize) {
|
||||
const iceberg = pack.ice.find(element => element.i === id);
|
||||
if (!iceberg) return;
|
||||
|
||||
const cellId = iceberg.cellId;
|
||||
const [cx, cy] = grid.points[cellId];
|
||||
const oldSize = iceberg.size;
|
||||
|
||||
const flat = iceberg.points.flat();
|
||||
const pairs = [];
|
||||
while (flat.length) pairs.push(flat.splice(0, 2));
|
||||
const poly = pairs.map(p => [(p[0] - cx) / oldSize, (p[1] - cy) / oldSize]);
|
||||
const points = poly.map(p => [
|
||||
rn(cx + p[0] * newSize, 2),
|
||||
rn(cy + p[1] * newSize, 2)
|
||||
]);
|
||||
|
||||
iceberg.points = points;
|
||||
iceberg.size = newSize;
|
||||
}
|
||||
|
||||
// Clear all ice
|
||||
function clear() {
|
||||
pack.ice = [];
|
||||
}
|
||||
|
||||
return {
|
||||
generate,
|
||||
addIceberg,
|
||||
removeIce,
|
||||
updateIceberg,
|
||||
randomizeIcebergShape,
|
||||
changeIcebergSize,
|
||||
clear
|
||||
};
|
||||
})();
|
||||
|
|
@ -574,3 +574,121 @@ function saveGeoJsonMarkers() {
|
|||
const fileName = getFileName("Markers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJsonZones() {
|
||||
const {zones, cells, vertices} = pack;
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
// Helper function to convert zone cells to polygon coordinates
|
||||
// Handles multiple disconnected components and holes properly
|
||||
function getZonePolygonCoordinates(zoneCells) {
|
||||
const cellsInZone = new Set(zoneCells);
|
||||
const ofSameType = (cellId) => cellsInZone.has(cellId);
|
||||
const ofDifferentType = (cellId) => !cellsInZone.has(cellId);
|
||||
|
||||
const checkedCells = new Set();
|
||||
const rings = []; // Array of LinearRings (each ring is an array of coordinates)
|
||||
|
||||
// Find all boundary components by tracing each connected region
|
||||
for (const cellId of zoneCells) {
|
||||
if (checkedCells.has(cellId)) continue;
|
||||
|
||||
// Check if this cell is on the boundary (has a neighbor outside the zone)
|
||||
const neighbors = cells.c[cellId];
|
||||
const onBorder = neighbors.some(ofDifferentType);
|
||||
if (!onBorder) continue;
|
||||
|
||||
// Check if this is an inner lake (hole) - skip if so
|
||||
const feature = pack.features[cells.f[cellId]];
|
||||
if (feature.type === "lake" && feature.shoreline) {
|
||||
if (feature.shoreline.every(ofSameType)) continue;
|
||||
}
|
||||
|
||||
// Find a starting vertex that's on the boundary
|
||||
const cellVertices = cells.v[cellId];
|
||||
let startingVertex = null;
|
||||
|
||||
for (const vertexId of cellVertices) {
|
||||
const vertexCells = vertices.c[vertexId];
|
||||
if (vertexCells.some(ofDifferentType)) {
|
||||
startingVertex = vertexId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startingVertex === null) continue;
|
||||
|
||||
// Use connectVertices to trace the boundary (reusing existing logic)
|
||||
const vertexChain = connectVertices({
|
||||
vertices,
|
||||
startingVertex,
|
||||
ofSameType,
|
||||
addToChecked: (cellId) => checkedCells.add(cellId),
|
||||
closeRing: false, // We'll close it manually after converting to coordinates
|
||||
});
|
||||
|
||||
if (vertexChain.length < 3) continue;
|
||||
|
||||
// Convert vertex chain to coordinates
|
||||
const coordinates = [];
|
||||
for (const vertexId of vertexChain) {
|
||||
const [x, y] = vertices.p[vertexId];
|
||||
coordinates.push(getCoordinates(x, y, 4));
|
||||
}
|
||||
|
||||
// Close the ring (first coordinate = last coordinate)
|
||||
if (coordinates.length > 0) {
|
||||
coordinates.push(coordinates[0]);
|
||||
}
|
||||
|
||||
// Only add ring if it has at least 4 positions (minimum for valid LinearRing)
|
||||
if (coordinates.length >= 4) {
|
||||
rings.push(coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
return rings;
|
||||
}
|
||||
|
||||
// Filter and process zones
|
||||
zones.forEach(zone => {
|
||||
// Exclude hidden zones and zones with no cells
|
||||
if (zone.hidden || !zone.cells || zone.cells.length === 0) return;
|
||||
|
||||
const rings = getZonePolygonCoordinates(zone.cells);
|
||||
|
||||
// Skip if no valid rings were generated
|
||||
if (rings.length === 0) return;
|
||||
|
||||
const properties = {
|
||||
id: zone.i,
|
||||
name: zone.name,
|
||||
type: zone.type,
|
||||
color: zone.color,
|
||||
cells: zone.cells
|
||||
};
|
||||
|
||||
// If there's only one ring, use Polygon geometry
|
||||
if (rings.length === 1) {
|
||||
const feature = {
|
||||
type: "Feature",
|
||||
geometry: {type: "Polygon", coordinates: rings},
|
||||
properties
|
||||
};
|
||||
json.features.push(feature);
|
||||
} else {
|
||||
// Multiple disconnected components: use MultiPolygon
|
||||
// Each component is wrapped in its own array
|
||||
const multiPolygonCoordinates = rings.map(ring => [ring]);
|
||||
const feature = {
|
||||
type: "Feature",
|
||||
geometry: {type: "MultiPolygon", coordinates: multiPolygonCoordinates},
|
||||
properties
|
||||
};
|
||||
json.features.push(feature);
|
||||
}
|
||||
});
|
||||
|
||||
const fileName = getFileName("Zones") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -246,6 +246,11 @@ window.Military = (function () {
|
|||
const expected = 3 * populationRate; // expected regiment size
|
||||
const mergeable = (n0, n1) => (!n0.s && !n1.s) || n0.u === n1.u; // check if regiments can be merged
|
||||
|
||||
// remove all existing regiment notes before regenerating
|
||||
for (let i = notes.length - 1; i >= 0; i--) {
|
||||
if (notes[i].id.startsWith("regiment")) notes.splice(i, 1);
|
||||
}
|
||||
|
||||
// get regiments for each state
|
||||
valid.forEach(s => {
|
||||
s.military = createRegiments(s.temp.platoons, s);
|
||||
|
|
@ -380,7 +385,14 @@ window.Military = (function () {
|
|||
: gauss(options.year - 100, 150, 1, options.year - 6);
|
||||
const conflict = campaign ? ` during the ${campaign.name}` : "";
|
||||
const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`;
|
||||
notes.push({id: `regiment${s.i}-${r.i}`, name: r.name, legend});
|
||||
const id = `regiment${s.i}-${r.i}`;
|
||||
const existing = notes.find(n => n.id === id);
|
||||
if (existing) {
|
||||
existing.name = r.name;
|
||||
existing.legend = legend;
|
||||
} else {
|
||||
notes.push({id, name: r.name, legend});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -330,15 +330,11 @@ function editHeightmap(options) {
|
|||
c.y = p[1];
|
||||
}
|
||||
|
||||
// recalculate zones to grid
|
||||
zones.selectAll("g").each(function () {
|
||||
const zone = d3.select(this);
|
||||
const dataCells = zone.attr("data-cells");
|
||||
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
const g = cells.map(i => pack.cells.g[i]);
|
||||
zone.attr("data-cells", g);
|
||||
zone.selectAll("*").remove();
|
||||
});
|
||||
const zoneGridCellsMap = new Map();
|
||||
for (const zone of pack.zones) {
|
||||
if (!zone.cells || !zone.cells.length) continue;
|
||||
zoneGridCellsMap.set(zone.i, zone.cells.map(i => pack.cells.g[i]));
|
||||
}
|
||||
|
||||
Features.markupGrid();
|
||||
if (erosionAllowed) addLakesInDeepDepressions();
|
||||
|
|
@ -448,24 +444,18 @@ function editHeightmap(options) {
|
|||
Lakes.defineNames();
|
||||
}
|
||||
|
||||
// restore zones from grid
|
||||
zones.selectAll("g").each(function () {
|
||||
const zone = d3.select(this);
|
||||
const g = zone.attr("data-cells");
|
||||
const gCells = g ? g.split(",").map(i => +i) : [];
|
||||
const cells = pack.cells.i.filter(i => gCells.includes(pack.cells.g[i]));
|
||||
const gridToPackMap = new Map();
|
||||
for (const i of pack.cells.i) {
|
||||
const g = pack.cells.g[i];
|
||||
if (!gridToPackMap.has(g)) gridToPackMap.set(g, []);
|
||||
gridToPackMap.get(g).push(i);
|
||||
}
|
||||
|
||||
zone.attr("data-cells", cells);
|
||||
zone.selectAll("*").remove();
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
zone
|
||||
.selectAll("*")
|
||||
.data(cells)
|
||||
.enter()
|
||||
.append("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("id", d => base + d);
|
||||
});
|
||||
for (const zone of pack.zones) {
|
||||
const gridCells = zoneGridCellsMap.get(zone.i);
|
||||
if (!gridCells || !gridCells.length) continue;
|
||||
zone.cells = gridCells.flatMap(g => gridToPackMap.get(g) || []);
|
||||
}
|
||||
|
||||
// recalculate ice
|
||||
Ice.generate();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue