mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-25 00:27:24 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator
This commit is contained in:
commit
669896757d
49 changed files with 7696 additions and 5770 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -1353,7 +1353,6 @@
|
|||
"integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
|
|
@ -1394,7 +1393,6 @@
|
|||
"integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
|
|
@ -1876,7 +1874,6 @@
|
|||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
@ -2163,7 +2160,6 @@
|
|||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -2475,7 +2471,6 @@
|
|||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
|
@ -2551,7 +2546,6 @@
|
|||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
|
|
|
|||
|
|
@ -1437,6 +1437,10 @@ div.states.hovered {
|
|||
background-image: linear-gradient(to right, #dedede 100%, #f2f2f2 50%, #fcfcfc 0%);
|
||||
}
|
||||
|
||||
#mergeStatesForm div[data-id].hovered {
|
||||
background-image: linear-gradient(to right, #dedede 100%, #f2f2f2 50%, #fcfcfc 0%);
|
||||
}
|
||||
|
||||
div.states > *,
|
||||
div.states sup,
|
||||
div.totalLine > div {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -253,8 +253,8 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
const source = findCell(s.x, s.y);
|
||||
const mouth = findCell(e.x, e.y);
|
||||
const name = Rivers.getName(mouth);
|
||||
const type = length < 25 ? rw({ Creek: 9, River: 3, Brook: 3, Stream: 1 }) : "River";
|
||||
pack.rivers.push({ i, parent: 0, length, source, mouth, basin: i, name, type });
|
||||
const type = length < 25 ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River";
|
||||
pack.rivers.push({i, parent: 0, length, source, mouth, basin: i, name, type});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +270,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era";
|
||||
const eraShort = era[0] + "E";
|
||||
const military = Military.getDefaultOptions();
|
||||
options = { winds, year, era, eraShort, military };
|
||||
options = {winds, year, era, eraShort, military};
|
||||
|
||||
// v1.3 added campaings data for all states
|
||||
States.generateCampaigns();
|
||||
|
|
@ -481,7 +481,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
if (isOlderThan("1.65.0")) {
|
||||
// v1.65 changed rivers data
|
||||
d3.select("#rivers").attr("style", null); // remove style to unhide layer
|
||||
const { cells, rivers } = pack;
|
||||
const {cells, rivers} = pack;
|
||||
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
|
||||
|
||||
for (const river of rivers) {
|
||||
|
|
@ -497,8 +497,8 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
|
||||
for (let i = 0; i <= segments; i++) {
|
||||
const shift = increment * i;
|
||||
const { x: x1, y: y1 } = node.getPointAtLength(length + shift);
|
||||
const { x: x2, y: y2 } = node.getPointAtLength(length - shift);
|
||||
const {x: x1, y: y1} = node.getPointAtLength(length + shift);
|
||||
const {x: x2, y: y2} = node.getPointAtLength(length - shift);
|
||||
const x = rn((x1 + x2) / 2, 1);
|
||||
const y = rn((y1 + y2) / 2, 1);
|
||||
|
||||
|
|
@ -565,7 +565,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
const fill = circle && circle.getAttribute("fill");
|
||||
const stroke = circle && circle.getAttribute("stroke");
|
||||
|
||||
const marker = { i, icon, type, x, y, size, cell };
|
||||
const marker = {i, icon, type, x, y, size, cell};
|
||||
if (size && size !== 30) marker.size = size;
|
||||
if (!isNaN(px) && px !== 12) marker.px = px;
|
||||
if (!isNaN(dx) && dx !== 50) marker.dx = dx;
|
||||
|
|
@ -631,7 +631,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
|
||||
if (isOlderThan("1.88.0")) {
|
||||
// v1.87 may have incorrect shield for some reason
|
||||
pack.states.forEach(({ coa }) => {
|
||||
pack.states.forEach(({coa}) => {
|
||||
if (coa?.shield === "state") delete coa.shield;
|
||||
});
|
||||
}
|
||||
|
|
@ -639,13 +639,13 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
if (isOlderThan("1.91.0")) {
|
||||
// from 1.91.00 custom coa is moved to coa object
|
||||
pack.states.forEach(state => {
|
||||
if (state.coa === "custom") state.coa = { custom: true };
|
||||
if (state.coa === "custom") state.coa = {custom: true};
|
||||
});
|
||||
pack.provinces.forEach(province => {
|
||||
if (province.coa === "custom") province.coa = { custom: true };
|
||||
if (province.coa === "custom") province.coa = {custom: true};
|
||||
});
|
||||
pack.burgs.forEach(burg => {
|
||||
if (burg.coa === "custom") burg.coa = { custom: true };
|
||||
if (burg.coa === "custom") burg.coa = {custom: true};
|
||||
});
|
||||
|
||||
// from 1.91.00 emblems don't have transform attribute
|
||||
|
|
@ -747,7 +747,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
const skip = terrs.attr("skip");
|
||||
const relax = terrs.attr("relax");
|
||||
|
||||
const curveTypes = { 0: "curveBasisClosed", 1: "curveLinear", 2: "curveStep" };
|
||||
const curveTypes = {0: "curveBasisClosed", 1: "curveLinear", 2: "curveStep"};
|
||||
const curve = curveTypes[terrs.attr("curve")] || "curveBasisClosed";
|
||||
|
||||
terrs
|
||||
|
|
@ -882,7 +882,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
const secondCellId = points[1][2];
|
||||
const feature = pack.cells.f[secondCellId];
|
||||
|
||||
pack.routes.push({ i: pack.routes.length, group, feature, points });
|
||||
pack.routes.push({i: pack.routes.length, group, feature, points});
|
||||
}
|
||||
}
|
||||
routes.selectAll("path").remove();
|
||||
|
|
@ -914,7 +914,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
const type = this.dataset.type;
|
||||
const color = this.getAttribute("fill");
|
||||
const cells = this.dataset.cells.split(",").map(Number);
|
||||
pack.zones.push({ i, name, type, cells, color });
|
||||
pack.zones.push({i, name, type, cells, color});
|
||||
});
|
||||
zones.style("display", null).selectAll("*").remove();
|
||||
if (layerIsOn("toggleZones")) drawZones();
|
||||
|
|
@ -975,7 +975,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
|
||||
if (isOlderThan("1.109.0")) {
|
||||
// v1.109.0 added customizable burg groups and icons
|
||||
options.burgs = { groups: [] };
|
||||
options.burgs = {groups: []};
|
||||
|
||||
burgIcons.selectAll("circle, use").each(function () {
|
||||
const group = this.parentNode.id;
|
||||
|
|
@ -987,7 +987,7 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
burgIcons.selectAll("g").each(function (_el, index) {
|
||||
const name = this.id;
|
||||
const isDefault = name === "towns";
|
||||
options.burgs.groups.push({ name, active: true, order: index + 1, isDefault, preview: "watabou-city" });
|
||||
options.burgs.groups.push({name, active: true, order: index + 1, isDefault, preview: "watabou-city"});
|
||||
if (!this.dataset.icon) this.dataset.icon = "#icon-circle";
|
||||
|
||||
const size = Number(this.getAttribute("size") || 2) * 2;
|
||||
|
|
@ -1104,6 +1104,12 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
// Re-render ice from migrated data
|
||||
if (layerIsOn("toggleIce")) drawIce();
|
||||
}
|
||||
}
|
||||
|
||||
if (isOlderThan("1.113.0")) {
|
||||
// v1.113.0 fixed issue with zone.cells getting rediculously long
|
||||
pack.zones.forEach(zone => {
|
||||
zone.cells = unique(zone.cells);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1290,10 +1290,10 @@ function openStateMergeDialog() {
|
|||
const statesSelector = validStates
|
||||
.map(
|
||||
s => /* html */ `
|
||||
<div data-tip="${s.fullName}">
|
||||
<div data-id="${s.i}" data-tip="${s.fullName}" style="cursor:default">
|
||||
<input type="radio" name="rulingState" value="${s.i}" />
|
||||
<input id="selectState${s.i}" class="checkbox" type="checkbox" name="statesToMerge" value="${s.i}"} />
|
||||
<label for="selectState${s.i}" class="checkbox-label">${emblem(s.i)}${s.fullName}</label>
|
||||
<input id="selectState${s.i}" class="checkbox" type="checkbox" name="statesToMerge" value="${s.i}" />
|
||||
<label for="selectState${s.i}" class="checkbox-label"><fill-box fill="${s.color}" disabled></fill-box>${emblem(s.i)}${s.fullName}</label>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
|
|
@ -1301,16 +1301,56 @@ function openStateMergeDialog() {
|
|||
|
||||
alertMessage.innerHTML = /* html */ `
|
||||
<form id='mergeStatesForm' style="overflow: hidden; display: flex; flex-direction: column; gap: 1em;">
|
||||
<header style='font-weight:bold;'>Select multiple states to merge and the ruling state to merge into</header>
|
||||
<p style="margin:0">
|
||||
Check the <b>checkbox</b> next to each state you want to merge.
|
||||
Use the <b>radio button</b> to pick the <em>ruling state</em> that will absorb all others (its name, color, and capital will be kept).
|
||||
Hover over a row to highlight the state on the map.
|
||||
</p>
|
||||
<main style='display: grid; grid-template-columns: 1fr 1fr; gap: .3em;'>
|
||||
${statesSelector}
|
||||
</main>
|
||||
</form>
|
||||
`;
|
||||
|
||||
byId("mergeStatesForm")
|
||||
.querySelectorAll("div[data-id]")
|
||||
.forEach(el => {
|
||||
el.addEventListener("mouseenter", highlightStateOnMergeHover);
|
||||
el.addEventListener("mouseleave", stateHighlightOff);
|
||||
});
|
||||
|
||||
function highlightStateOnMergeHover(event) {
|
||||
if (!layerIsOn("toggleStates")) return;
|
||||
const state = +event.currentTarget.dataset.id;
|
||||
if (!state) return;
|
||||
const d = regions.select("#state" + state).attr("d");
|
||||
if (!d) return;
|
||||
|
||||
stateHighlightOff();
|
||||
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
const totalLength = path.node().getTotalLength();
|
||||
const duration = (totalLength + 5000) / 2;
|
||||
const interpolate = d3.interpolateString(`0, ${totalLength}`, `${totalLength}, ${totalLength}`);
|
||||
path
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.attrTween("stroke-dasharray", () => interpolate);
|
||||
}
|
||||
|
||||
$("#alert").dialog({
|
||||
width: fitContent(),
|
||||
width: 600,
|
||||
title: `Merge states`,
|
||||
close: stateHighlightOff,
|
||||
buttons: {
|
||||
Merge: function () {
|
||||
const formData = new FormData(byId("mergeStatesForm"));
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ async function parseLoadedData(data, mapVersion) {
|
|||
|
||||
{
|
||||
// dynamically import and run auto-update script
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.109.4");
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.113.0");
|
||||
resolveVersionConflicts(mapVersion);
|
||||
}
|
||||
|
||||
|
|
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (document.getElementById("diplomacyEditor")?.offsetParent) highlightEditorLine(diplomacyEditor, state);
|
||||
if (document.getElementById("militaryOverview")?.offsetParent) highlightEditorLine(militaryOverview, state);
|
||||
if (document.getElementById("provincesEditor")?.offsetParent) highlightEditorLine(provincesEditor, province);
|
||||
if (document.getElementById("mergeStatesForm")?.offsetParent) highlightEditorLine(byId("mergeStatesForm"), state);
|
||||
} else if (layerIsOn("toggleCultures") && pack.cells.culture[i]) {
|
||||
const culture = pack.cells.culture[i];
|
||||
tip("Culture: " + pack.cultures[culture].name);
|
||||
|
|
@ -246,7 +247,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
}
|
||||
|
||||
function highlightEditorLine(editor, id, timeout = 10000) {
|
||||
Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered
|
||||
Array.from(editor.getElementsByClassName("hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered
|
||||
const hovered = Array.from(editor.querySelectorAll("div")).find(el => el.dataset.id == id);
|
||||
if (hovered) hovered.classList.add("hovered"); // add hovered class
|
||||
if (timeout)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
function editHeightmap(options) {
|
||||
const {mode, tool} = options || {};
|
||||
restartHistory();
|
||||
viewbox.selectAll("#heights").remove();
|
||||
viewbox.insert("g", "#terrs").attr("id", "heights");
|
||||
|
||||
if (!mode) showModeDialog();
|
||||
|
|
@ -188,7 +189,8 @@ function editHeightmap(options) {
|
|||
|
||||
// restore initial layers
|
||||
drawFeatures();
|
||||
byId("heights").remove();
|
||||
viewbox.selectAll("#heights").remove();
|
||||
|
||||
turnButtonOff("toggleHeight");
|
||||
document
|
||||
.getElementById("mapLayers")
|
||||
|
|
@ -330,15 +332,13 @@ 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();
|
||||
});
|
||||
// save zone grid cells to restore them later
|
||||
const zoneGridCellsMap = new Map();
|
||||
for (const zone of pack.zones) {
|
||||
if (!zone.cells?.length) continue;
|
||||
const zoneGridCells = zone.cells.map(i => pack.cells.g[i]);
|
||||
zoneGridCellsMap.set(zone.i, unique(zoneGridCells));
|
||||
}
|
||||
|
||||
Features.markupGrid();
|
||||
if (erosionAllowed) addLakesInDeepDepressions();
|
||||
|
|
@ -448,24 +448,23 @@ 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);
|
||||
});
|
||||
// restore zone cells
|
||||
for (const zone of pack.zones) {
|
||||
const gridCells = zoneGridCellsMap.get(zone.i);
|
||||
if (gridCells?.length) {
|
||||
const packCells = gridCells.flatMap(g => gridToPackMap.get(g) || []);
|
||||
zone.cells = unique(packCells);
|
||||
} else {
|
||||
zone.cells = [];
|
||||
}
|
||||
}
|
||||
|
||||
// recalculate ice
|
||||
Ice.generate();
|
||||
|
|
@ -675,7 +674,10 @@ function editHeightmap(options) {
|
|||
if (power === 0) return tip("Power should not be zero", false, "error");
|
||||
|
||||
const heights = grid.cells.h;
|
||||
const operation = power > 0 ? HeightmapGenerator.addRange.bind(HeightmapGenerator) : HeightmapGenerator.addTrough.bind(HeightmapGenerator);
|
||||
const operation =
|
||||
power > 0
|
||||
? HeightmapGenerator.addRange.bind(HeightmapGenerator)
|
||||
: HeightmapGenerator.addTrough.bind(HeightmapGenerator);
|
||||
HeightmapGenerator.setGraph(grid);
|
||||
operation("1", String(Math.abs(power)), null, null, fromCell, toCell);
|
||||
const changedHeights = HeightmapGenerator.getHeights();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
|
||||
*/
|
||||
|
||||
const VERSION = "1.112.1";
|
||||
const VERSION = "1.113.1";
|
||||
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
|
||||
|
||||
{
|
||||
|
|
@ -49,6 +49,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
|
|||
<li>New routes generation algorithm</li>
|
||||
<li>Routes overview tool</li>
|
||||
<li>Configurable longitude</li>
|
||||
<li>Export zones to GeoJSON</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
|
@ -138,13 +138,32 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<link rel="preload" href="index.css?v=1.109.1" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="index.css?v=1.113.1"
|
||||
as="style"
|
||||
onload="
|
||||
this.onload = null;
|
||||
this.rel = 'stylesheet';
|
||||
"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="icons.css"
|
||||
as="style"
|
||||
onload="
|
||||
this.onload = null;
|
||||
this.rel = 'stylesheet';
|
||||
"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="libs/jquery-ui.css?v=1.106.5"
|
||||
as="style"
|
||||
onload="this.onload=null; this.rel='stylesheet'"
|
||||
onload="
|
||||
this.onload = null;
|
||||
this.rel = 'stylesheet';
|
||||
"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -4289,7 +4308,9 @@
|
|||
id="templateCA"
|
||||
data-tip="Find or share custom template on Cartography Assets portal"
|
||||
class="icon-drafting-compass"
|
||||
onclick="openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/templates')"
|
||||
onclick="
|
||||
openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/templates')
|
||||
"
|
||||
></button>
|
||||
<button
|
||||
id="templateTutorial"
|
||||
|
|
@ -6120,7 +6141,10 @@
|
|||
id="showLabels"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
onchange="hideLabels.checked = !this.checked; invokeActiveZooming()"
|
||||
onchange="
|
||||
hideLabels.checked = !this.checked;
|
||||
invokeActiveZooming();
|
||||
"
|
||||
checked=""
|
||||
/>
|
||||
<label for="showLabels" class="checkbox-label">Show all labels</label>
|
||||
|
|
@ -6152,6 +6176,7 @@
|
|||
<button onclick="saveGeoJsonRoutes()" data-tip="Download routes data in GeoJSON format">routes</button>
|
||||
<button onclick="saveGeoJsonRivers()" data-tip="Download rivers data in GeoJSON format">rivers</button>
|
||||
<button onclick="saveGeoJsonMarkers()" data-tip="Download markers data in GeoJSON format">markers</button>
|
||||
<button onclick="saveGeoJsonZones()" data-tip="Download zones data in GeoJSON format">zones</button>
|
||||
</div>
|
||||
<p>
|
||||
GeoJSON format is used in GIS tools such as QGIS. Check out
|
||||
|
|
@ -8494,19 +8519,16 @@
|
|||
|
||||
<script defer src="config/heightmap-templates.js"></script>
|
||||
<script defer src="config/precreated-heightmaps.js"></script>
|
||||
<script defer src="modules/military-generator.js?v=1.107.0"></script>
|
||||
<script defer src="modules/markers-generator.js?v=1.107.0"></script>
|
||||
<script defer src="modules/coa-generator.js?v=1.99.00"></script>
|
||||
<script defer src="modules/military-generator.js?v=1.112.3"></script>
|
||||
<script defer src="modules/resample.js?v=1.112.1"></script>
|
||||
<script defer src="libs/alea.min.js?v1.105.0"></script>
|
||||
<script defer src="libs/polylabel.min.js?v1.105.0"></script>
|
||||
<script defer src="libs/lineclip.min.js?v1.105.0"></script>
|
||||
<script defer src="libs/simplify.js?v1.105.6"></script>
|
||||
<script defer src="modules/fonts.js?v=1.99.03"></script>
|
||||
<script defer src="modules/ui/layers.js?v=1.111.0"></script>
|
||||
<script defer src="modules/ui/measurers.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/style-presets.js?v=1.100.00"></script>
|
||||
<script defer src="modules/ui/general.js?v=1.100.00"></script>
|
||||
<script defer src="modules/ui/general.js?v=1.113.1"></script>
|
||||
<script defer src="modules/ui/options.js?v=1.106.2"></script>
|
||||
<script defer src="main.js?v=1.111.0"></script>
|
||||
|
||||
|
|
@ -8514,7 +8536,7 @@
|
|||
<script defer src="modules/ui/editors.js?v=1.112.1"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.111.0"></script>
|
||||
<script defer src="modules/ui/world-configurator.js?v=1.105.4"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js?v=1.105.2"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js?v=1.113.0"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js?v=1.108.1"></script>
|
||||
<script defer src="modules/ui/biomes-editor.js?v=1.112.0"></script>
|
||||
<script defer src="modules/ui/namesbase-editor.js?v=1.105.11"></script>
|
||||
|
|
@ -8551,12 +8573,11 @@
|
|||
<script defer src="modules/ui/submap-tool.js?v=1.106.2"></script>
|
||||
<script defer src="modules/ui/transform-tool.js?v=1.106.2"></script>
|
||||
<script defer src="modules/ui/hotkeys.js?v=1.104.0"></script>
|
||||
<script defer src="modules/coa-renderer.js?v=1.99.00"></script>
|
||||
<script defer src="libs/rgbquant.min.js"></script>
|
||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
<script defer src="modules/io/save.js?v=1.111.0"></script>
|
||||
<script defer src="modules/io/load.js?v=1.111.0"></script>
|
||||
<script defer src="modules/io/load.js?v=1.113.0"></script>
|
||||
<script defer src="modules/io/cloud.js?v=1.106.0"></script>
|
||||
<script defer src="modules/io/export.js?v=1.108.13"></script>
|
||||
<script defer src="modules/io/export.js?v=1.112.2"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ class BurgModule {
|
|||
? "City"
|
||||
: burg.type;
|
||||
burg.coa = COA.generate(stateCOA, kinship, null, type);
|
||||
burg.coa.shield = COA.getShield(burg.culture, burg.state);
|
||||
burg.coa.shield = COA.getShield(burg.culture!, burg.state!);
|
||||
}
|
||||
|
||||
private defineFeatures(burg: Burg) {
|
||||
|
|
|
|||
53
src/modules/emblem/box.ts
Normal file
53
src/modules/emblem/box.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// shield-specific size multiplier
|
||||
export const shieldBox = {
|
||||
heater: "0 10 200 200",
|
||||
spanish: "0 10 200 200",
|
||||
french: "0 10 200 200",
|
||||
|
||||
horsehead: "0 10 200 200",
|
||||
horsehead2: "0 10 200 200",
|
||||
polish: "0 0 200 200",
|
||||
hessen: "0 5 200 200",
|
||||
swiss: "0 10 200 200",
|
||||
|
||||
boeotian: "0 0 200 200",
|
||||
roman: "0 0 200 200",
|
||||
kite: "0 0 200 200",
|
||||
oldFrench: "0 10 200 200",
|
||||
renaissance: "0 5 200 200",
|
||||
baroque: "0 10 200 200",
|
||||
|
||||
targe: "0 0 200 200",
|
||||
targe2: "0 0 200 200",
|
||||
pavise: "0 0 200 200",
|
||||
wedged: "0 10 200 200",
|
||||
|
||||
flag: "0 0 200 200",
|
||||
pennon: "2.5 0 200 200",
|
||||
guidon: "2.5 0 200 200",
|
||||
banner: "0 10 200 200",
|
||||
dovetail: "0 10 200 200",
|
||||
gonfalon: "0 10 200 200",
|
||||
pennant: "0 0 200 200",
|
||||
|
||||
round: "0 0 200 200",
|
||||
oval: "0 0 200 200",
|
||||
vesicaPiscis: "0 0 200 200",
|
||||
square: "0 0 200 200",
|
||||
diamond: "0 0 200 200",
|
||||
no: "0 0 200 200",
|
||||
|
||||
fantasy1: "0 0 200 200",
|
||||
fantasy2: "0 5 200 200",
|
||||
fantasy3: "0 5 200 200",
|
||||
fantasy4: "0 5 200 200",
|
||||
fantasy5: "0 0 200 200",
|
||||
|
||||
noldor: "0 0 200 200",
|
||||
gondor: "0 5 200 200",
|
||||
easterling: "0 0 200 200",
|
||||
erebor: "0 0 200 200",
|
||||
ironHills: "0 5 200 200",
|
||||
urukHai: "0 0 200 200",
|
||||
moriaOrc: "0 0 200 200",
|
||||
};
|
||||
883
src/modules/emblem/chargeData.ts
Normal file
883
src/modules/emblem/chargeData.ts
Normal file
|
|
@ -0,0 +1,883 @@
|
|||
export interface ChargeDataEntry {
|
||||
colors?: number;
|
||||
sinister?: boolean;
|
||||
reversed?: boolean;
|
||||
positions?: Record<string, number>;
|
||||
natural?: string;
|
||||
}
|
||||
|
||||
export const chargeData: Record<string, ChargeDataEntry> = {
|
||||
agnusDei: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
angel: {
|
||||
colors: 2,
|
||||
positions: { e: 1 },
|
||||
},
|
||||
anvil: {
|
||||
sinister: true,
|
||||
},
|
||||
apple: {
|
||||
colors: 2,
|
||||
},
|
||||
arbalest: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
archer: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
armEmbowedHoldingSabre: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
armEmbowedVambraced: {
|
||||
sinister: true,
|
||||
},
|
||||
armEmbowedVambracedHoldingSword: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
armillarySphere: {
|
||||
positions: { e: 1 },
|
||||
},
|
||||
arrow: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
arrowsSheaf: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
axe: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
badgerStatant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
banner: {
|
||||
colors: 2,
|
||||
},
|
||||
basilisk: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
bearPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
bearRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
bee: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
bell: {
|
||||
colors: 2,
|
||||
},
|
||||
boarHeadErased: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
boarRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 12, beh: 1, kn: 1, jln: 2 },
|
||||
},
|
||||
boat: {
|
||||
colors: 2,
|
||||
},
|
||||
bookClosed: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
bookClosed2: {
|
||||
sinister: true,
|
||||
},
|
||||
bookOpen: {
|
||||
colors: 3,
|
||||
},
|
||||
bow: {
|
||||
sinister: true,
|
||||
},
|
||||
bowWithArrow: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
bowWithThreeArrows: {
|
||||
colors: 3,
|
||||
},
|
||||
bucket: {
|
||||
colors: 2,
|
||||
},
|
||||
bugleHorn: {
|
||||
colors: 2,
|
||||
},
|
||||
bugleHorn2: {
|
||||
colors: 2,
|
||||
},
|
||||
bullHeadCaboshed: {
|
||||
colors: 2,
|
||||
},
|
||||
bullPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
butterfly: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
camel: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
cancer: {
|
||||
reversed: true,
|
||||
},
|
||||
cannon: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
caravel: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
castle: {
|
||||
colors: 2,
|
||||
},
|
||||
castle2: {
|
||||
colors: 3,
|
||||
},
|
||||
catPassantGuardant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
cavalier: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 1 },
|
||||
},
|
||||
centaur: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
chalice: {
|
||||
colors: 2,
|
||||
},
|
||||
cinquefoil: {
|
||||
reversed: true,
|
||||
},
|
||||
cock: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
comet: {
|
||||
reversed: true,
|
||||
},
|
||||
cowStatant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
cossack: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
crescent: {
|
||||
reversed: true,
|
||||
},
|
||||
crocodile: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
crosier: {
|
||||
sinister: true,
|
||||
},
|
||||
crossbow: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
crossGamma: {
|
||||
sinister: true,
|
||||
},
|
||||
crossLatin: {
|
||||
reversed: true,
|
||||
},
|
||||
crossTau: {
|
||||
reversed: true,
|
||||
},
|
||||
crossTriquetra: {
|
||||
reversed: true,
|
||||
},
|
||||
crown: {
|
||||
colors: 2,
|
||||
positions: {
|
||||
e: 10,
|
||||
abcdefgzi: 1,
|
||||
beh: 3,
|
||||
behdf: 2,
|
||||
acegi: 1,
|
||||
kn: 1,
|
||||
pq: 2,
|
||||
abc: 1,
|
||||
jln: 4,
|
||||
jleh: 1,
|
||||
def: 2,
|
||||
abcpqh: 3,
|
||||
},
|
||||
},
|
||||
crown2: {
|
||||
colors: 3,
|
||||
positions: {
|
||||
e: 10,
|
||||
abcdefgzi: 1,
|
||||
beh: 3,
|
||||
behdf: 2,
|
||||
acegi: 1,
|
||||
kn: 1,
|
||||
pq: 2,
|
||||
abc: 1,
|
||||
jln: 4,
|
||||
jleh: 1,
|
||||
def: 2,
|
||||
abcpqh: 3,
|
||||
},
|
||||
},
|
||||
deerHeadCaboshed: {
|
||||
colors: 2,
|
||||
},
|
||||
dolphin: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
donkeyHeadCaboshed: {
|
||||
colors: 2,
|
||||
},
|
||||
dove: {
|
||||
colors: 2,
|
||||
natural: "argent",
|
||||
sinister: true,
|
||||
},
|
||||
doveDisplayed: {
|
||||
colors: 2,
|
||||
natural: "argent",
|
||||
sinister: true,
|
||||
},
|
||||
dragonfly: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
dragonPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
dragonRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
drakkar: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
drawingCompass: {
|
||||
sinister: true,
|
||||
},
|
||||
drum: {
|
||||
colors: 3,
|
||||
},
|
||||
duck: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
eagle: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 15, beh: 1, kn: 1, abc: 1, jlh: 2, def: 2, pq: 1 },
|
||||
},
|
||||
eagleTwoHeads: {
|
||||
colors: 3,
|
||||
},
|
||||
elephant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
elephantHeadErased: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
falchion: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
falcon: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
fan: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
fasces: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
feather: {
|
||||
sinister: true,
|
||||
},
|
||||
flamberge: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
flangedMace: {
|
||||
reversed: true,
|
||||
},
|
||||
fly: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
foot: {
|
||||
sinister: true,
|
||||
},
|
||||
fountain: {
|
||||
natural: "azure",
|
||||
},
|
||||
frog: {
|
||||
reversed: true,
|
||||
},
|
||||
garb: {
|
||||
colors: 2,
|
||||
natural: "or",
|
||||
positions: {
|
||||
e: 1,
|
||||
def: 3,
|
||||
abc: 2,
|
||||
beh: 1,
|
||||
kn: 1,
|
||||
jln: 3,
|
||||
jleh: 1,
|
||||
abcpqh: 1,
|
||||
joe: 1,
|
||||
lme: 1,
|
||||
},
|
||||
},
|
||||
gauntlet: {
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
goat: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
goutte: {
|
||||
reversed: true,
|
||||
},
|
||||
grapeBunch: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
grapeBunch2: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
grenade: {
|
||||
colors: 2,
|
||||
},
|
||||
greyhoundCourant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
},
|
||||
greyhoundRampant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
},
|
||||
greyhoundSejant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
griffinPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1 },
|
||||
},
|
||||
griffinRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
},
|
||||
hand: {
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
positions: { e: 10, jln: 2, kn: 1, jeo: 1, abc: 2, pqe: 1 },
|
||||
},
|
||||
harp: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
hatchet: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
head: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
positions: { e: 1 },
|
||||
},
|
||||
headWreathed: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 1 },
|
||||
},
|
||||
hedgehog: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
helmet: {
|
||||
sinister: true,
|
||||
},
|
||||
helmetCorinthian: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
helmetGreat: {
|
||||
sinister: true,
|
||||
},
|
||||
helmetZischagge: {
|
||||
sinister: true,
|
||||
},
|
||||
heron: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
hindStatant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
hook: {
|
||||
sinister: true,
|
||||
},
|
||||
horseHeadCouped: {
|
||||
sinister: true,
|
||||
},
|
||||
horsePassant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
horseRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
horseSalient: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
horseshoe: {
|
||||
reversed: true,
|
||||
},
|
||||
hourglass: {
|
||||
colors: 3,
|
||||
},
|
||||
ladybird: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
lamb: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
lambPassantReguardant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
lanceWithBanner: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
laurelWreath: {
|
||||
colors: 2,
|
||||
},
|
||||
lighthouse: {
|
||||
colors: 3,
|
||||
},
|
||||
lionHeadCaboshed: {
|
||||
colors: 2,
|
||||
},
|
||||
lionHeadErased: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
lionPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
},
|
||||
lionPassantGuardant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
lionRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1 },
|
||||
},
|
||||
lionSejant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
lizard: {
|
||||
reversed: true,
|
||||
},
|
||||
lochaberAxe: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
log: {
|
||||
sinister: true,
|
||||
},
|
||||
lute: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
lymphad: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 1 },
|
||||
},
|
||||
mace: {
|
||||
colors: 2,
|
||||
},
|
||||
maces: {
|
||||
colors: 2,
|
||||
},
|
||||
mallet: {
|
||||
colors: 2,
|
||||
},
|
||||
mantle: {
|
||||
colors: 3,
|
||||
},
|
||||
martenCourant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
mascle: {
|
||||
positions: {
|
||||
e: 15,
|
||||
abcdefgzi: 3,
|
||||
beh: 3,
|
||||
bdefh: 4,
|
||||
acegi: 1,
|
||||
kn: 3,
|
||||
joe: 2,
|
||||
abc: 3,
|
||||
jlh: 8,
|
||||
jleh: 1,
|
||||
df: 3,
|
||||
abcpqh: 4,
|
||||
pqe: 3,
|
||||
eknpq: 3,
|
||||
},
|
||||
},
|
||||
mastiffStatant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
mitre: {
|
||||
colors: 3,
|
||||
},
|
||||
monk: {
|
||||
sinister: true,
|
||||
},
|
||||
moonInCrescent: {
|
||||
sinister: true,
|
||||
},
|
||||
mullet: {
|
||||
reversed: true,
|
||||
},
|
||||
mullet7: {
|
||||
reversed: true,
|
||||
},
|
||||
oak: {
|
||||
colors: 3,
|
||||
},
|
||||
orb: {
|
||||
colors: 3,
|
||||
},
|
||||
ouroboros: {
|
||||
sinister: true,
|
||||
},
|
||||
owl: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
owlDisplayed: {
|
||||
colors: 2,
|
||||
},
|
||||
palmTree: {
|
||||
colors: 3,
|
||||
},
|
||||
parrot: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
peacock: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
peacockInPride: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
pear: {
|
||||
colors: 2,
|
||||
},
|
||||
pegasus: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
pike: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
pineTree: {
|
||||
colors: 2,
|
||||
},
|
||||
plaice: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
plough: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
ploughshare: {
|
||||
sinister: true,
|
||||
},
|
||||
porcupine: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
portcullis: {
|
||||
colors: 2,
|
||||
},
|
||||
rabbitSejant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
rake: {
|
||||
reversed: true,
|
||||
},
|
||||
rapier: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
ramHeadErased: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
ramPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
ratRampant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
raven: {
|
||||
colors: 2,
|
||||
natural: "sable",
|
||||
sinister: true,
|
||||
positions: { e: 15, beh: 1, kn: 1, jeo: 1, abc: 3, jln: 3, def: 1 },
|
||||
},
|
||||
rhinoceros: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
rose: {
|
||||
colors: 3,
|
||||
},
|
||||
sabre: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
sabre2: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
sabresCrossed: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
sagittarius: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
salmon: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
saw: {
|
||||
colors: 2,
|
||||
},
|
||||
scale: {
|
||||
colors: 2,
|
||||
},
|
||||
scaleImbalanced: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
scissors: {
|
||||
reversed: true,
|
||||
},
|
||||
scorpion: {
|
||||
reversed: true,
|
||||
},
|
||||
scrollClosed: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
scythe: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
scythe2: {
|
||||
sinister: true,
|
||||
},
|
||||
serpent: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
shield: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
sickle: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
snail: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
snake: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
spear: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
spiral: {
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
squirrel: {
|
||||
sinister: true,
|
||||
},
|
||||
stagLodgedRegardant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
stagPassant: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
stirrup: {
|
||||
colors: 2,
|
||||
},
|
||||
swallow: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
swan: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
swanErased: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
sword: {
|
||||
colors: 2,
|
||||
reversed: true,
|
||||
},
|
||||
talbotPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
talbotSejant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
tower: {
|
||||
colors: 2,
|
||||
},
|
||||
tree: {
|
||||
positions: { e: 1 },
|
||||
},
|
||||
trefoil: {
|
||||
reversed: true,
|
||||
},
|
||||
trowel: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
reversed: true,
|
||||
},
|
||||
unicornRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
wasp: {
|
||||
colors: 3,
|
||||
reversed: true,
|
||||
},
|
||||
wheatStalk: {
|
||||
colors: 2,
|
||||
},
|
||||
windmill: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
wing: {
|
||||
sinister: true,
|
||||
},
|
||||
wingSword: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
wolfHeadErased: {
|
||||
colors: 2,
|
||||
sinister: true,
|
||||
},
|
||||
wolfPassant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
},
|
||||
wolfRampant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
wolfStatant: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
wyvern: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
positions: { e: 10, jln: 1 },
|
||||
},
|
||||
wyvernWithWingsDisplayed: {
|
||||
colors: 3,
|
||||
sinister: true,
|
||||
},
|
||||
};
|
||||
477
src/modules/emblem/charges.ts
Normal file
477
src/modules/emblem/charges.ts
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
import { chargeData } from "./chargeData";
|
||||
|
||||
export const charges = {
|
||||
types: {
|
||||
conventional: 33, // 40 charges
|
||||
crosses: 13, // 30 charges
|
||||
beasts: 7, // 41 charges
|
||||
beastHeads: 3, // 10 charges
|
||||
birds: 3, // 16 charges
|
||||
reptiles: 2, // 5 charges
|
||||
bugs: 2, // 8 charges
|
||||
fishes: 1, // 3 charges
|
||||
molluscs: 1, // 2 charges
|
||||
plants: 3, // 18 charges
|
||||
fantastic: 5, // 14 charges
|
||||
agriculture: 2, // 8 charges
|
||||
arms: 5, // 32 charges
|
||||
bodyparts: 2, // 12 charges
|
||||
people: 2, // 4 charges
|
||||
architecture: 3, // 11 charges
|
||||
seafaring: 3, // 9 charges
|
||||
tools: 3, // 15 charges
|
||||
miscellaneous: 5, // 30 charges
|
||||
inescutcheon: 3, // 43 charges
|
||||
ornaments: 0, // 9 charges
|
||||
uploaded: 0,
|
||||
},
|
||||
single: {
|
||||
conventional: 10,
|
||||
crosses: 8,
|
||||
beasts: 7,
|
||||
beastHeads: 3,
|
||||
birds: 3,
|
||||
reptiles: 2,
|
||||
bugs: 2,
|
||||
fishes: 1,
|
||||
molluscs: 1,
|
||||
plants: 3,
|
||||
fantastic: 5,
|
||||
agriculture: 2,
|
||||
arms: 5,
|
||||
bodyparts: 2,
|
||||
people: 2,
|
||||
architecture: 3,
|
||||
seafaring: 3,
|
||||
tools: 3,
|
||||
miscellaneous: 5,
|
||||
inescutcheon: 1,
|
||||
},
|
||||
semy: {
|
||||
conventional: 4,
|
||||
crosses: 1,
|
||||
},
|
||||
conventional: {
|
||||
annulet: 4,
|
||||
billet: 5,
|
||||
carreau: 1,
|
||||
comet: 1,
|
||||
compassRose: 1,
|
||||
crescent: 5,
|
||||
delf: 0,
|
||||
estoile: 1,
|
||||
fleurDeLis: 6,
|
||||
fountain: 1,
|
||||
fusil: 4,
|
||||
gear: 1,
|
||||
goutte: 4,
|
||||
heart: 4,
|
||||
lozenge: 2,
|
||||
lozengeFaceted: 3,
|
||||
lozengePloye: 1,
|
||||
mascle: 4,
|
||||
moonInCrescent: 1,
|
||||
mullet: 5,
|
||||
mullet10: 1,
|
||||
mullet4: 3,
|
||||
mullet6: 4,
|
||||
mullet6Faceted: 1,
|
||||
mullet6Pierced: 1,
|
||||
mullet7: 1,
|
||||
mullet8: 1,
|
||||
mulletFaceted: 1,
|
||||
mulletPierced: 1,
|
||||
pique: 2,
|
||||
roundel: 4,
|
||||
roundel2: 3,
|
||||
rustre: 2,
|
||||
spiral: 1,
|
||||
sun: 3,
|
||||
sunInSplendour: 1,
|
||||
sunInSplendour2: 1,
|
||||
trefle: 2,
|
||||
triangle: 3,
|
||||
trianglePierced: 1,
|
||||
},
|
||||
crosses: {
|
||||
crossHummetty: 15,
|
||||
crossVoided: 1,
|
||||
crossPattee: 2,
|
||||
crossPatteeAlisee: 1,
|
||||
crossFormee: 1,
|
||||
crossFormee2: 2,
|
||||
crossPotent: 2,
|
||||
crossJerusalem: 1,
|
||||
crosslet: 1,
|
||||
crossClechy: 3,
|
||||
crossBottony: 1,
|
||||
crossFleury: 3,
|
||||
crossPatonce: 1,
|
||||
crossPommy: 1,
|
||||
crossGamma: 1,
|
||||
crossArrowed: 1,
|
||||
crossFitchy: 1,
|
||||
crossCercelee: 1,
|
||||
crossMoline: 2,
|
||||
crossFourchy: 1,
|
||||
crossAvellane: 1,
|
||||
crossErminee: 1,
|
||||
crossBiparted: 1,
|
||||
crossMaltese: 3,
|
||||
crossTemplar: 2,
|
||||
crossCeltic: 1,
|
||||
crossCeltic2: 1,
|
||||
crossTriquetra: 1,
|
||||
crossCarolingian: 1,
|
||||
crossOccitan: 1,
|
||||
crossSaltire: 3,
|
||||
crossBurgundy: 1,
|
||||
crossLatin: 3,
|
||||
crossPatriarchal: 1,
|
||||
crossOrthodox: 1,
|
||||
crossCalvary: 1,
|
||||
crossDouble: 1,
|
||||
crossTau: 1,
|
||||
crossSantiago: 1,
|
||||
crossAnkh: 1,
|
||||
},
|
||||
beasts: {
|
||||
agnusDei: 1,
|
||||
badgerStatant: 1,
|
||||
bearPassant: 1,
|
||||
bearRampant: 3,
|
||||
boarRampant: 1,
|
||||
bullPassant: 1,
|
||||
camel: 1,
|
||||
catPassantGuardant: 1,
|
||||
cowStatant: 1,
|
||||
dolphin: 1,
|
||||
elephant: 1,
|
||||
goat: 1,
|
||||
greyhoundCourant: 1,
|
||||
greyhoundRampant: 1,
|
||||
greyhoundSejant: 1,
|
||||
hedgehog: 1,
|
||||
hindStatant: 1,
|
||||
horsePassant: 1,
|
||||
horseRampant: 2,
|
||||
horseSalient: 1,
|
||||
lamb: 1,
|
||||
lambPassantReguardant: 1,
|
||||
lionPassant: 3,
|
||||
lionPassantGuardant: 2,
|
||||
lionRampant: 7,
|
||||
lionSejant: 2,
|
||||
martenCourant: 1,
|
||||
mastiffStatant: 1,
|
||||
porcupine: 1,
|
||||
rabbitSejant: 1,
|
||||
ramPassant: 1,
|
||||
ratRampant: 1,
|
||||
rhinoceros: 1,
|
||||
squirrel: 1,
|
||||
stagLodgedRegardant: 1,
|
||||
stagPassant: 1,
|
||||
talbotPassant: 1,
|
||||
talbotSejant: 1,
|
||||
wolfPassant: 1,
|
||||
wolfRampant: 1,
|
||||
wolfStatant: 1,
|
||||
},
|
||||
beastHeads: {
|
||||
boarHeadErased: 1,
|
||||
bullHeadCaboshed: 1,
|
||||
deerHeadCaboshed: 1,
|
||||
donkeyHeadCaboshed: 1,
|
||||
elephantHeadErased: 1,
|
||||
horseHeadCouped: 1,
|
||||
lionHeadCaboshed: 2,
|
||||
lionHeadErased: 2,
|
||||
ramHeadErased: 1,
|
||||
wolfHeadErased: 2,
|
||||
},
|
||||
birds: {
|
||||
cock: 3,
|
||||
dove: 2,
|
||||
doveDisplayed: 1,
|
||||
duck: 1,
|
||||
eagle: 9,
|
||||
falcon: 2,
|
||||
heron: 1,
|
||||
owl: 1,
|
||||
owlDisplayed: 1,
|
||||
parrot: 1,
|
||||
peacock: 1,
|
||||
peacockInPride: 1,
|
||||
raven: 2,
|
||||
swallow: 1,
|
||||
swan: 2,
|
||||
swanErased: 1,
|
||||
},
|
||||
reptiles: {
|
||||
crocodile: 1,
|
||||
frog: 1,
|
||||
lizard: 1,
|
||||
ouroboros: 1,
|
||||
snake: 1,
|
||||
},
|
||||
bugs: {
|
||||
bee: 1,
|
||||
butterfly: 1,
|
||||
cancer: 1,
|
||||
dragonfly: 1,
|
||||
fly: 1,
|
||||
ladybird: 1,
|
||||
scorpion: 1,
|
||||
wasp: 1,
|
||||
},
|
||||
fishes: {
|
||||
pike: 1,
|
||||
plaice: 1,
|
||||
salmon: 1,
|
||||
},
|
||||
molluscs: {
|
||||
escallop: 4,
|
||||
snail: 1,
|
||||
},
|
||||
plants: {
|
||||
apple: 1,
|
||||
cinquefoil: 1,
|
||||
earOfWheat: 1,
|
||||
grapeBunch: 1,
|
||||
grapeBunch2: 1,
|
||||
mapleLeaf: 1,
|
||||
oak: 1,
|
||||
palmTree: 1,
|
||||
pear: 1,
|
||||
pineCone: 1,
|
||||
pineTree: 1,
|
||||
quatrefoil: 1,
|
||||
rose: 1,
|
||||
sextifoil: 1,
|
||||
thistle: 1,
|
||||
tree: 1,
|
||||
trefoil: 1,
|
||||
wheatStalk: 1,
|
||||
},
|
||||
fantastic: {
|
||||
angel: 3,
|
||||
basilisk: 1,
|
||||
centaur: 1,
|
||||
dragonPassant: 3,
|
||||
dragonRampant: 2,
|
||||
eagleTwoHeads: 2,
|
||||
griffinPassant: 1,
|
||||
griffinRampant: 2,
|
||||
pegasus: 1,
|
||||
sagittarius: 1,
|
||||
serpent: 1,
|
||||
unicornRampant: 1,
|
||||
wyvern: 1,
|
||||
wyvernWithWingsDisplayed: 1,
|
||||
},
|
||||
agriculture: {
|
||||
garb: 2,
|
||||
millstone: 1,
|
||||
plough: 1,
|
||||
ploughshare: 1,
|
||||
rake: 1,
|
||||
scythe: 1,
|
||||
scythe2: 1,
|
||||
sickle: 1,
|
||||
},
|
||||
arms: {
|
||||
arbalest: 1,
|
||||
arbalest2: 1,
|
||||
arrow: 1,
|
||||
arrowsSheaf: 1,
|
||||
axe: 3,
|
||||
bow: 1,
|
||||
bowWithArrow: 2,
|
||||
bowWithThreeArrows: 1,
|
||||
cannon: 1,
|
||||
falchion: 1,
|
||||
flamberge: 1,
|
||||
flangedMace: 1,
|
||||
gauntlet: 1,
|
||||
grenade: 1,
|
||||
hatchet: 3,
|
||||
helmet: 2,
|
||||
helmetCorinthian: 1,
|
||||
helmetGreat: 2,
|
||||
helmetZischagge: 1,
|
||||
lanceHead: 1,
|
||||
lanceWithBanner: 1,
|
||||
lochaberAxe: 1,
|
||||
mace: 1,
|
||||
maces: 1,
|
||||
mallet: 1,
|
||||
rapier: 1,
|
||||
sabre: 1,
|
||||
sabre2: 1,
|
||||
sabresCrossed: 1,
|
||||
shield: 1,
|
||||
spear: 1,
|
||||
sword: 4,
|
||||
},
|
||||
bodyparts: {
|
||||
armEmbowedHoldingSabre: 1,
|
||||
armEmbowedVambraced: 1,
|
||||
armEmbowedVambracedHoldingSword: 1,
|
||||
bone: 1,
|
||||
crossedBones: 2,
|
||||
foot: 1,
|
||||
hand: 4,
|
||||
head: 1,
|
||||
headWreathed: 1,
|
||||
skeleton: 2,
|
||||
skull: 2,
|
||||
skull2: 1,
|
||||
},
|
||||
people: {
|
||||
archer: 1,
|
||||
cavalier: 3,
|
||||
cossack: 1,
|
||||
monk: 1,
|
||||
},
|
||||
architecture: {
|
||||
bridge: 1,
|
||||
bridge2: 1,
|
||||
castle: 2,
|
||||
castle2: 1,
|
||||
column: 1,
|
||||
lighthouse: 1,
|
||||
palace: 1,
|
||||
pillar: 1,
|
||||
portcullis: 1,
|
||||
tower: 2,
|
||||
windmill: 1,
|
||||
},
|
||||
seafaring: {
|
||||
anchor: 6,
|
||||
armillarySphere: 1,
|
||||
boat: 2,
|
||||
boat2: 1,
|
||||
caravel: 1,
|
||||
drakkar: 1,
|
||||
lymphad: 2,
|
||||
raft: 1,
|
||||
shipWheel: 1,
|
||||
},
|
||||
tools: {
|
||||
anvil: 2,
|
||||
drawingCompass: 2,
|
||||
fan: 1,
|
||||
hook: 1,
|
||||
ladder: 1,
|
||||
ladder2: 1,
|
||||
pincers: 1,
|
||||
saw: 1,
|
||||
scale: 1,
|
||||
scaleImbalanced: 1,
|
||||
scalesHanging: 1,
|
||||
scissors: 1,
|
||||
scissors2: 1,
|
||||
shears: 1,
|
||||
trowel: 1,
|
||||
},
|
||||
miscellaneous: {
|
||||
attire: 2,
|
||||
banner: 2,
|
||||
bell: 3,
|
||||
bookClosed: 1,
|
||||
bookClosed2: 1,
|
||||
bookOpen: 1,
|
||||
bucket: 1,
|
||||
buckle: 1,
|
||||
bugleHorn: 2,
|
||||
bugleHorn2: 1,
|
||||
chain: 2,
|
||||
chalice: 2,
|
||||
cowHorns: 3,
|
||||
crosier: 1,
|
||||
crown: 3,
|
||||
crown2: 2,
|
||||
drum: 1,
|
||||
fasces: 1,
|
||||
feather: 3,
|
||||
harp: 2,
|
||||
horseshoe: 3,
|
||||
hourglass: 2,
|
||||
key: 3,
|
||||
laurelWreath: 2,
|
||||
laurelWreath2: 1,
|
||||
log: 1,
|
||||
lute: 2,
|
||||
lyre: 1,
|
||||
mitre: 1,
|
||||
orb: 1,
|
||||
pot: 2,
|
||||
ramsHorn: 1,
|
||||
sceptre: 1,
|
||||
scrollClosed: 1,
|
||||
snowflake: 1,
|
||||
stagsAttires: 1,
|
||||
stirrup: 2,
|
||||
wheel: 3,
|
||||
wing: 2,
|
||||
wingSword: 1,
|
||||
},
|
||||
inescutcheon: {
|
||||
inescutcheonHeater: 1,
|
||||
inescutcheonSpanish: 1,
|
||||
inescutcheonFrench: 1,
|
||||
inescutcheonHorsehead: 1,
|
||||
inescutcheonHorsehead2: 1,
|
||||
inescutcheonPolish: 1,
|
||||
inescutcheonHessen: 1,
|
||||
inescutcheonSwiss: 1,
|
||||
inescutcheonBoeotian: 1,
|
||||
inescutcheonRoman: 1,
|
||||
inescutcheonKite: 1,
|
||||
inescutcheonOldFrench: 1,
|
||||
inescutcheonRenaissance: 1,
|
||||
inescutcheonBaroque: 1,
|
||||
inescutcheonTarge: 1,
|
||||
inescutcheonTarge2: 1,
|
||||
inescutcheonPavise: 1,
|
||||
inescutcheonWedged: 1,
|
||||
inescutcheonFlag: 1,
|
||||
inescutcheonPennon: 1,
|
||||
inescutcheonGuidon: 1,
|
||||
inescutcheonBanner: 1,
|
||||
inescutcheonDovetail: 1,
|
||||
inescutcheonGonfalon: 1,
|
||||
inescutcheonPennant: 1,
|
||||
inescutcheonRound: 1,
|
||||
inescutcheonOval: 1,
|
||||
inescutcheonVesicaPiscis: 1,
|
||||
inescutcheonSquare: 1,
|
||||
inescutcheonDiamond: 1,
|
||||
inescutcheonNo: 1,
|
||||
inescutcheonFantasy1: 1,
|
||||
inescutcheonFantasy2: 1,
|
||||
inescutcheonFantasy3: 1,
|
||||
inescutcheonFantasy4: 1,
|
||||
inescutcheonFantasy5: 1,
|
||||
inescutcheonNoldor: 1,
|
||||
inescutcheonGondor: 1,
|
||||
inescutcheonEasterling: 1,
|
||||
inescutcheonErebor: 1,
|
||||
inescutcheonIronHills: 1,
|
||||
inescutcheonUrukHai: 1,
|
||||
inescutcheonMoriaOrc: 1,
|
||||
},
|
||||
ornaments: {
|
||||
mantle: 0,
|
||||
ribbon1: 3,
|
||||
ribbon2: 2,
|
||||
ribbon3: 1,
|
||||
ribbon4: 1,
|
||||
ribbon5: 1,
|
||||
ribbon6: 1,
|
||||
ribbon7: 1,
|
||||
ribbon8: 1,
|
||||
},
|
||||
data: chargeData,
|
||||
};
|
||||
12
src/modules/emblem/colors.ts
Normal file
12
src/modules/emblem/colors.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export const colors = {
|
||||
argent: "#fafafa",
|
||||
or: "#ffe066",
|
||||
gules: "#d7374a",
|
||||
sable: "#333333",
|
||||
azure: "#377cd7",
|
||||
vert: "#26c061",
|
||||
purpure: "#522d5b",
|
||||
murrey: "#85185b",
|
||||
sanguine: "#b63a3a",
|
||||
tenné: "#cc7f19",
|
||||
};
|
||||
46
src/modules/emblem/divisions.ts
Normal file
46
src/modules/emblem/divisions.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { lineWeights } from "./lineWeights";
|
||||
|
||||
export const divisions = {
|
||||
variants: {
|
||||
perPale: 5,
|
||||
perFess: 5,
|
||||
perBend: 2,
|
||||
perBendSinister: 1,
|
||||
perChevron: 1,
|
||||
perChevronReversed: 1,
|
||||
perCross: 5,
|
||||
perPile: 1,
|
||||
perSaltire: 1,
|
||||
gyronny: 1,
|
||||
chevronny: 1,
|
||||
},
|
||||
perPale: lineWeights,
|
||||
perFess: lineWeights,
|
||||
perBend: lineWeights,
|
||||
perBendSinister: lineWeights,
|
||||
perChevron: lineWeights,
|
||||
perChevronReversed: lineWeights,
|
||||
perCross: {
|
||||
straight: 20,
|
||||
wavy: 5,
|
||||
engrailed: 4,
|
||||
invecked: 3,
|
||||
rayonne: 1,
|
||||
embattled: 1,
|
||||
raguly: 1,
|
||||
urdy: 1,
|
||||
indented: 2,
|
||||
dentilly: 1,
|
||||
bevilled: 1,
|
||||
angled: 1,
|
||||
embattledGhibellin: 1,
|
||||
embattledGrady: 1,
|
||||
dovetailedIndented: 1,
|
||||
dovetailed: 1,
|
||||
potenty: 1,
|
||||
potentyDexter: 1,
|
||||
potentySinister: 1,
|
||||
nebuly: 1,
|
||||
},
|
||||
perPile: lineWeights,
|
||||
};
|
||||
591
src/modules/emblem/generator.ts
Normal file
591
src/modules/emblem/generator.ts
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
import { P, rw } from "../../utils";
|
||||
import { charges } from "./charges";
|
||||
import { divisions } from "./divisions";
|
||||
import { lineWeights } from "./lineWeights";
|
||||
import { ordinaries } from "./ordinaries";
|
||||
import { positions } from "./positions";
|
||||
import { shields } from "./shields";
|
||||
import { createTinctures } from "./tinctures";
|
||||
import { typeMapping } from "./typeMapping";
|
||||
|
||||
declare global {
|
||||
var COA: EmblemGeneratorModule;
|
||||
}
|
||||
|
||||
export interface EmblemCharge {
|
||||
charge: string;
|
||||
t: string;
|
||||
p: string;
|
||||
t2?: string;
|
||||
t3?: string;
|
||||
size?: number;
|
||||
sinister?: number;
|
||||
reversed?: number;
|
||||
divided?: string;
|
||||
}
|
||||
|
||||
export interface EmblemOrdinary {
|
||||
ordinary: string;
|
||||
t: string;
|
||||
line?: string;
|
||||
divided?: string;
|
||||
above?: boolean;
|
||||
}
|
||||
|
||||
export interface EmblemDivision {
|
||||
division: string;
|
||||
t: string;
|
||||
line?: string;
|
||||
}
|
||||
|
||||
export interface Emblem {
|
||||
t1: string;
|
||||
shield?: string;
|
||||
division?: EmblemDivision;
|
||||
ordinaries?: EmblemOrdinary[];
|
||||
charges?: EmblemCharge[];
|
||||
custom?: boolean;
|
||||
}
|
||||
|
||||
class EmblemGeneratorModule {
|
||||
generate(
|
||||
parent: Emblem | null,
|
||||
kinship: number | null,
|
||||
dominion: number | null,
|
||||
type?: string,
|
||||
): Emblem {
|
||||
if (!parent || parent.custom) {
|
||||
parent = null;
|
||||
kinship = 0;
|
||||
dominion = 0;
|
||||
}
|
||||
|
||||
let usedPattern: string | null = null;
|
||||
const usedTinctures: string[] = [];
|
||||
|
||||
const t1 = P(kinship as number)
|
||||
? parent!.t1
|
||||
: this.getTincture("field", usedTinctures, null);
|
||||
if (t1.includes("-")) usedPattern = t1;
|
||||
const coa: Emblem = { t1 };
|
||||
|
||||
const addCharge = P(usedPattern ? 0.5 : 0.93); // 80% for charge
|
||||
const linedOrdinary =
|
||||
(addCharge && P(0.3)) || P(0.5)
|
||||
? parent?.ordinaries && P(kinship as number)
|
||||
? parent.ordinaries[0].ordinary
|
||||
: rw(ordinaries.lined)
|
||||
: null;
|
||||
|
||||
const ordinary =
|
||||
(!addCharge && P(0.65)) || P(0.3)
|
||||
? linedOrdinary
|
||||
? linedOrdinary
|
||||
: rw(ordinaries.straight)
|
||||
: null; // 36% for ordinary
|
||||
|
||||
const rareDivided = [
|
||||
"chief",
|
||||
"terrace",
|
||||
"chevron",
|
||||
"quarter",
|
||||
"flaunches",
|
||||
].includes(ordinary!);
|
||||
|
||||
const divisioned = (() => {
|
||||
if (rareDivided) return P(0.03);
|
||||
if (addCharge && ordinary) return P(0.03);
|
||||
if (addCharge) return P(0.3);
|
||||
if (ordinary) return P(0.7);
|
||||
return P(0.995);
|
||||
})();
|
||||
|
||||
const division = (() => {
|
||||
if (divisioned) {
|
||||
if (parent?.division && P((kinship as number) - 0.1))
|
||||
return parent.division.division;
|
||||
return rw(divisions.variants);
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
if (division) {
|
||||
const t = this.getTincture(
|
||||
"division",
|
||||
usedTinctures,
|
||||
P(0.98) ? coa.t1 : null,
|
||||
);
|
||||
coa.division = { division, t };
|
||||
if (divisions[division as keyof typeof divisions])
|
||||
coa.division.line =
|
||||
usedPattern || (ordinary && P(0.7))
|
||||
? "straight"
|
||||
: rw(divisions[division as keyof typeof divisions]);
|
||||
}
|
||||
|
||||
if (ordinary) {
|
||||
coa.ordinaries = [
|
||||
{ ordinary, t: this.getTincture("charge", usedTinctures, coa.t1) },
|
||||
];
|
||||
if (linedOrdinary)
|
||||
coa.ordinaries[0].line =
|
||||
usedPattern || (division && P(0.7)) ? "straight" : rw(lineWeights);
|
||||
if (
|
||||
division &&
|
||||
!addCharge &&
|
||||
!usedPattern &&
|
||||
P(0.5) &&
|
||||
ordinary !== "bordure" &&
|
||||
ordinary !== "orle"
|
||||
) {
|
||||
if (P(0.8)) coa.ordinaries[0].divided = "counter";
|
||||
// 40%
|
||||
else if (P(0.6)) coa.ordinaries[0].divided = "field";
|
||||
// 6%
|
||||
else coa.ordinaries[0].divided = "division"; // 4%
|
||||
}
|
||||
}
|
||||
|
||||
if (addCharge) {
|
||||
const charge = (() => {
|
||||
if (parent?.charges && P((kinship as number) - 0.1))
|
||||
return parent.charges[0].charge;
|
||||
if (type && type !== "Generic" && P(0.3)) return rw(typeMapping[type]);
|
||||
return this.selectCharge(
|
||||
ordinary || divisioned ? charges.types : charges.single,
|
||||
);
|
||||
})();
|
||||
const chargeDataEntry = charges.data[charge] || {};
|
||||
|
||||
let p: string;
|
||||
let t: string;
|
||||
|
||||
const ordinaryData = ordinaries.data[ordinary!];
|
||||
const tOrdinary = coa.ordinaries ? coa.ordinaries[0].t : null;
|
||||
|
||||
if (ordinaryData?.positionsOn && P(0.8)) {
|
||||
// place charge over ordinary (use tincture of field type)
|
||||
p = rw(ordinaryData.positionsOn);
|
||||
t =
|
||||
!usedPattern && P(0.3)
|
||||
? coa.t1
|
||||
: this.getTincture("charge", [], tOrdinary);
|
||||
} else if (ordinaryData?.positionsOff && P(0.95)) {
|
||||
// place charge out of ordinary (use tincture of ordinary type)
|
||||
p = rw(ordinaryData.positionsOff);
|
||||
t =
|
||||
!usedPattern && P(0.3)
|
||||
? tOrdinary!
|
||||
: this.getTincture("charge", usedTinctures, coa.t1);
|
||||
} else if (
|
||||
positions.divisions[division as keyof typeof positions.divisions]
|
||||
) {
|
||||
// place charge in fields made by division
|
||||
p = rw(
|
||||
positions.divisions[division as keyof typeof positions.divisions],
|
||||
);
|
||||
t = this.getTincture(
|
||||
"charge",
|
||||
tOrdinary ? usedTinctures.concat(tOrdinary) : usedTinctures,
|
||||
coa.t1,
|
||||
);
|
||||
} else if (chargeDataEntry.positions) {
|
||||
// place charge-suitable position
|
||||
p = rw(chargeDataEntry.positions);
|
||||
t = this.getTincture("charge", usedTinctures, coa.t1);
|
||||
} else {
|
||||
// place in standard position (use new tincture)
|
||||
p = usedPattern
|
||||
? "e"
|
||||
: charges.conventional[charge as keyof typeof charges.conventional]
|
||||
? rw(positions.conventional)
|
||||
: rw(positions.complex);
|
||||
t = this.getTincture(
|
||||
"charge",
|
||||
usedTinctures.concat(tOrdinary!),
|
||||
coa.t1,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
chargeDataEntry.natural &&
|
||||
chargeDataEntry.natural !== t &&
|
||||
chargeDataEntry.natural !== tOrdinary
|
||||
)
|
||||
t = chargeDataEntry.natural;
|
||||
|
||||
const item: EmblemCharge = { charge: charge, t, p };
|
||||
const colors = chargeDataEntry.colors || 1;
|
||||
if (colors > 1)
|
||||
item.t2 = P(0.25)
|
||||
? this.getTincture("charge", usedTinctures, coa.t1)
|
||||
: t;
|
||||
if (colors > 2 && item.t2)
|
||||
item.t3 = P(0.5)
|
||||
? this.getTincture("charge", usedTinctures, coa.t1)
|
||||
: t;
|
||||
coa.charges = [item];
|
||||
|
||||
if (p === "ABCDEFGHIJKL" && P(0.95)) {
|
||||
// add central charge if charge is in bordure
|
||||
coa.charges[0].charge = rw(charges.conventional);
|
||||
const chargeNew = this.selectCharge(charges.single);
|
||||
const tNew = this.getTincture("charge", usedTinctures, coa.t1);
|
||||
coa.charges.push({ charge: chargeNew, t: tNew, p: "e" });
|
||||
} else if (P(0.8) && charge === "inescutcheon") {
|
||||
// add charge to inescutcheon
|
||||
const chargeNew = this.selectCharge(charges.types);
|
||||
const t2 = this.getTincture("charge", [], t);
|
||||
coa.charges.push({ charge: chargeNew, t: t2, p, size: 0.5 });
|
||||
} else if (division && !ordinary) {
|
||||
const allowCounter =
|
||||
!usedPattern &&
|
||||
(!coa.division?.line || coa.division.line === "straight");
|
||||
|
||||
// dimidiation: second charge at division basic positions
|
||||
if (
|
||||
P(0.3) &&
|
||||
["perPale", "perFess"].includes(division) &&
|
||||
coa.division?.line === "straight"
|
||||
) {
|
||||
coa.charges[0].divided = "field";
|
||||
if (P(0.95)) {
|
||||
const p2 =
|
||||
p === "e" || P(0.5)
|
||||
? "e"
|
||||
: rw(
|
||||
positions.divisions[
|
||||
division as keyof typeof positions.divisions
|
||||
],
|
||||
);
|
||||
const chargeNew = this.selectCharge(charges.single);
|
||||
const tNew = this.getTincture(
|
||||
"charge",
|
||||
usedTinctures,
|
||||
coa.division!.t,
|
||||
);
|
||||
coa.charges.push({
|
||||
charge: chargeNew,
|
||||
t: tNew,
|
||||
p: p2,
|
||||
divided: "division",
|
||||
});
|
||||
}
|
||||
} else if (allowCounter && P(0.4)) coa.charges[0].divided = "counter";
|
||||
// counterchanged, 40%
|
||||
else if (
|
||||
["perPale", "perFess", "perBend", "perBendSinister"].includes(
|
||||
division,
|
||||
) &&
|
||||
P(0.8)
|
||||
) {
|
||||
// place 2 charges in division standard positions
|
||||
const [p1, p2] =
|
||||
division === "perPale"
|
||||
? ["p", "q"]
|
||||
: division === "perFess"
|
||||
? ["k", "n"]
|
||||
: division === "perBend"
|
||||
? ["l", "m"]
|
||||
: ["j", "o"]; // perBendSinister
|
||||
coa.charges[0].p = p1;
|
||||
|
||||
const chargeNew = this.selectCharge(charges.single);
|
||||
const tNew = this.getTincture(
|
||||
"charge",
|
||||
usedTinctures,
|
||||
coa.division!.t,
|
||||
);
|
||||
coa.charges.push({ charge: chargeNew, t: tNew, p: p2 });
|
||||
} else if (["perCross", "perSaltire"].includes(division) && P(0.5)) {
|
||||
// place 4 charges in division standard positions
|
||||
const [p1, p2, p3, p4] =
|
||||
division === "perCross"
|
||||
? ["j", "l", "m", "o"]
|
||||
: ["b", "d", "f", "h"];
|
||||
coa.charges[0].p = p1;
|
||||
|
||||
const c2 = this.selectCharge(charges.single);
|
||||
const t2 = this.getTincture("charge", [], coa.division!.t);
|
||||
|
||||
const c3 = this.selectCharge(charges.single);
|
||||
const t3 = this.getTincture("charge", [], coa.division!.t);
|
||||
|
||||
const c4 = this.selectCharge(charges.single);
|
||||
const t4 = this.getTincture("charge", [], coa.t1);
|
||||
coa.charges.push(
|
||||
{ charge: c2, t: t2, p: p2 },
|
||||
{ charge: c3, t: t3, p: p3 },
|
||||
{ charge: c4, t: t4, p: p4 },
|
||||
);
|
||||
} else if (allowCounter && p.length > 1)
|
||||
coa.charges[0].divided = "counter"; // counterchanged, 40%
|
||||
}
|
||||
|
||||
for (const c of coa.charges) {
|
||||
this.defineChargeAttributes(ordinary, division, c);
|
||||
}
|
||||
}
|
||||
|
||||
// dominions have canton with parent coa
|
||||
if (P(dominion as number) && parent?.charges) {
|
||||
const invert = this.isSameType(parent.t1, coa.t1);
|
||||
const t = invert
|
||||
? this.getTincture("division", usedTinctures, coa.t1)
|
||||
: parent.t1;
|
||||
const canton: EmblemOrdinary = { ordinary: "canton", t };
|
||||
|
||||
if (coa.charges) {
|
||||
for (let i = coa.charges.length - 1; i >= 0; i--) {
|
||||
const charge = coa.charges[i];
|
||||
if (charge.size === 1.5) charge.size = 1.4;
|
||||
charge.p = charge.p.replaceAll(/[ajy]/g, "");
|
||||
if (!charge.p) coa.charges.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
let charge = parent.charges[0].charge;
|
||||
if (charge === "inescutcheon" && parent.charges[1])
|
||||
charge = parent.charges[1].charge;
|
||||
|
||||
let t2 = invert ? parent.t1 : parent.charges[0].t;
|
||||
if (this.isSameType(t, t2))
|
||||
t2 = this.getTincture("charge", usedTinctures, t);
|
||||
|
||||
if (!coa.charges) coa.charges = [];
|
||||
coa.charges.push({ charge, t: t2, p: "y", size: 0.5 });
|
||||
|
||||
if (coa.ordinaries) {
|
||||
coa.ordinaries.push(canton);
|
||||
} else {
|
||||
coa.ordinaries = [canton];
|
||||
}
|
||||
}
|
||||
|
||||
return coa;
|
||||
}
|
||||
|
||||
private selectCharge(set?: Record<string, number>): string {
|
||||
const type = set ? rw(set) : rw(charges.types);
|
||||
return type === "inescutcheon"
|
||||
? "inescutcheon"
|
||||
: rw(charges[type as keyof typeof charges] as Record<string, number>);
|
||||
}
|
||||
|
||||
// Select tincture: element type (field, division, charge), used field tinctures, field type to follow RoT
|
||||
private getTincture(
|
||||
element: "field" | "division" | "charge",
|
||||
fields: string[] = [],
|
||||
RoT: string | null,
|
||||
): string {
|
||||
const base = RoT ? (RoT.includes("-") ? RoT.split("-")[1] : RoT) : null;
|
||||
const tinctures = createTinctures();
|
||||
|
||||
let type = rw(tinctures[element]); // metals, colours, stains, patterns
|
||||
if (RoT && type !== "patterns")
|
||||
type = this.getType(base!) === "metals" ? "colours" : "metals"; // follow RoT
|
||||
if (type === "metals" && fields.includes("or") && fields.includes("argent"))
|
||||
type = "colours"; // exclude metals overuse
|
||||
let tincture = rw(
|
||||
tinctures[type as keyof typeof tinctures] as Record<string, number>,
|
||||
);
|
||||
|
||||
while (tincture === base || fields.includes(tincture)) {
|
||||
tincture = rw(
|
||||
tinctures[type as keyof typeof tinctures] as Record<string, number>,
|
||||
);
|
||||
} // follow RoT
|
||||
|
||||
if (type !== "patterns" && element !== "charge") fields.push(tincture); // add field tincture
|
||||
|
||||
if (type === "patterns") {
|
||||
tincture = this.definePattern(tincture, element, fields);
|
||||
}
|
||||
|
||||
return tincture;
|
||||
}
|
||||
|
||||
private defineChargeAttributes(
|
||||
ordinary: string | null,
|
||||
division: string | null,
|
||||
c: EmblemCharge,
|
||||
): void {
|
||||
// define size
|
||||
c.size = (c.size || 1) * this.getSize(c.p, ordinary, division);
|
||||
|
||||
// clean-up position
|
||||
c.p = [...new Set(c.p)].join("");
|
||||
|
||||
// define orientation
|
||||
if (P(0.02) && charges.data[c.charge]?.sinister) c.sinister = 1;
|
||||
if (P(0.02) && charges.data[c.charge]?.reversed) c.reversed = 1;
|
||||
}
|
||||
|
||||
private getType(t: string): string | undefined {
|
||||
const tinc = t.includes("-") ? t.split("-")[1] : t;
|
||||
const tinctures = createTinctures();
|
||||
if (Object.keys(tinctures.metals).includes(tinc)) return "metals";
|
||||
if (Object.keys(tinctures.colours).includes(tinc)) return "colours";
|
||||
if (Object.keys(tinctures.stains).includes(tinc)) return "stains";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private isSameType(t1: string, t2: string): boolean {
|
||||
return this.typeOf(t1) === this.typeOf(t2);
|
||||
}
|
||||
|
||||
private typeOf(tinc: string): string {
|
||||
const tinctures = createTinctures();
|
||||
if (Object.keys(tinctures.metals).includes(tinc)) return "metals";
|
||||
if (Object.keys(tinctures.colours).includes(tinc)) return "colours";
|
||||
if (Object.keys(tinctures.stains).includes(tinc)) return "stains";
|
||||
return "pattern";
|
||||
}
|
||||
|
||||
private definePattern(
|
||||
pattern: string,
|
||||
element: "field" | "division" | "charge",
|
||||
usedTinctures: string[],
|
||||
): string {
|
||||
let t1: string | null = null;
|
||||
let t2: string | null = null;
|
||||
let size = "";
|
||||
|
||||
// Size selection - must use sequential P() calls to match original behavior
|
||||
if (P(0.1)) size = "-small";
|
||||
// biome-ignore lint/suspicious/noDuplicateElseIf: <explanation>
|
||||
else if (P(0.1)) size = "-smaller";
|
||||
else if (P(0.01)) size = "-big";
|
||||
else if (P(0.005)) size = "-smallest";
|
||||
|
||||
// apply standard tinctures
|
||||
if (P(0.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {
|
||||
t1 = "azure";
|
||||
t2 = "argent";
|
||||
} else if (P(0.8) && pattern === "ermine") {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
} else if (pattern === "pappellony") {
|
||||
if (P(0.2)) {
|
||||
t1 = "gules";
|
||||
t2 = "or";
|
||||
// biome-ignore lint/suspicious/noDuplicateElseIf: <explanation>
|
||||
} else if (P(0.2)) {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
// biome-ignore lint/suspicious/noDuplicateElseIf: <explanation>
|
||||
} else if (P(0.2)) {
|
||||
t1 = "azure";
|
||||
t2 = "argent";
|
||||
}
|
||||
} else if (pattern === "masoned") {
|
||||
if (P(0.3)) {
|
||||
t1 = "gules";
|
||||
t2 = "argent";
|
||||
// biome-ignore lint/suspicious/noDuplicateElseIf: <explanation>
|
||||
} else if (P(0.3)) {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
} else if (P(0.1)) {
|
||||
t1 = "or";
|
||||
t2 = "sable";
|
||||
}
|
||||
} else if (pattern === "fretty") {
|
||||
if (t2 === "sable" || P(0.35)) {
|
||||
t1 = "argent";
|
||||
t2 = "gules";
|
||||
} else if (P(0.25)) {
|
||||
t1 = "sable";
|
||||
t2 = "or";
|
||||
} else if (P(0.15)) {
|
||||
t1 = "gules";
|
||||
t2 = "argent";
|
||||
}
|
||||
} else if (pattern === "semy")
|
||||
pattern = `${pattern}_of_${this.selectCharge(charges.semy)}`;
|
||||
|
||||
if (!t1 || !t2) {
|
||||
const tinctures = createTinctures();
|
||||
const startWithMetal = P(0.7);
|
||||
t1 = startWithMetal ? rw(tinctures.metals) : rw(tinctures.colours);
|
||||
t2 = startWithMetal ? rw(tinctures.colours) : rw(tinctures.metals);
|
||||
}
|
||||
|
||||
// division should not be the same tincture as base field
|
||||
if (element === "division") {
|
||||
if (usedTinctures.includes(t1)) t1 = this.replaceTincture(t1);
|
||||
if (usedTinctures.includes(t2)) t2 = this.replaceTincture(t2);
|
||||
}
|
||||
|
||||
usedTinctures.push(t1, t2);
|
||||
return `${pattern}-${t1}-${t2}${size}`;
|
||||
}
|
||||
|
||||
private replaceTincture(t: string): string {
|
||||
const type = this.getType(t);
|
||||
let n: string | null = null;
|
||||
const tinctures = createTinctures();
|
||||
while (!n || n === t) {
|
||||
n = rw(
|
||||
tinctures[type as keyof typeof tinctures] as Record<string, number>,
|
||||
);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private getSize(
|
||||
p: string,
|
||||
o: string | null = null,
|
||||
d: string | null = null,
|
||||
): number {
|
||||
if (p === "e" && (o === "bordure" || o === "orle")) return 1.1;
|
||||
if (p === "e") return 1.5;
|
||||
if (p === "jln" || p === "jlh") return 0.7;
|
||||
if (p === "abcpqh" || p === "ez" || p === "be") return 0.5;
|
||||
if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p))
|
||||
return 0.5;
|
||||
if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross")
|
||||
return 0.6;
|
||||
if (p.length > 10) return 0.18; // >10 (bordure)
|
||||
if (p.length > 7) return 0.3; // 8, 9, 10
|
||||
if (p.length > 4) return 0.4; // 5, 6, 7
|
||||
if (p.length > 2) return 0.5; // 3, 4
|
||||
return 0.7; // 1, 2
|
||||
}
|
||||
|
||||
getShield(culture: number, state?: number): string {
|
||||
const emblemShape = document.getElementById(
|
||||
"emblemShape",
|
||||
) as HTMLSelectElement | null;
|
||||
const shapeGroup =
|
||||
emblemShape?.selectedOptions[0]?.parentElement?.getAttribute("label") ||
|
||||
"Diversiform";
|
||||
if (shapeGroup !== "Diversiform") return emblemShape!.value;
|
||||
|
||||
if (emblemShape?.value === "state" && state && pack.states[state].coa)
|
||||
return pack.states[state].coa!.shield!;
|
||||
if (pack.cultures[culture].shield) return pack.cultures[culture].shield!;
|
||||
ERROR &&
|
||||
console.error(
|
||||
"Shield shape is not defined on culture level",
|
||||
pack.cultures[culture],
|
||||
);
|
||||
return "heater";
|
||||
}
|
||||
|
||||
toString(coa: Emblem): string {
|
||||
return JSON.stringify(coa).replaceAll("#", "%23");
|
||||
}
|
||||
|
||||
copy(coa: Emblem): Emblem {
|
||||
return JSON.parse(JSON.stringify(coa));
|
||||
}
|
||||
|
||||
get shields() {
|
||||
return shields;
|
||||
}
|
||||
}
|
||||
|
||||
export default EmblemGeneratorModule;
|
||||
|
||||
window.COA = new EmblemGeneratorModule();
|
||||
2
src/modules/emblem/index.ts
Normal file
2
src/modules/emblem/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import "./generator";
|
||||
import "./renderer";
|
||||
37
src/modules/emblem/lineWeights.ts
Normal file
37
src/modules/emblem/lineWeights.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Line weights for random selection
|
||||
// Different from lines.ts which contains SVG path data for rendering
|
||||
export const lineWeights = {
|
||||
straight: 50,
|
||||
wavy: 8,
|
||||
engrailed: 4,
|
||||
invecked: 3,
|
||||
rayonne: 3,
|
||||
embattled: 1,
|
||||
raguly: 1,
|
||||
urdy: 1,
|
||||
dancetty: 1,
|
||||
indented: 2,
|
||||
dentilly: 1,
|
||||
bevilled: 1,
|
||||
angled: 1,
|
||||
flechy: 1,
|
||||
barby: 1,
|
||||
enclavy: 1,
|
||||
escartely: 1,
|
||||
arched: 2,
|
||||
archedReversed: 1,
|
||||
nowy: 1,
|
||||
nowyReversed: 1,
|
||||
embattledGhibellin: 1,
|
||||
embattledNotched: 1,
|
||||
embattledGrady: 1,
|
||||
dovetailedIndented: 1,
|
||||
dovetailed: 1,
|
||||
potenty: 1,
|
||||
potentyDexter: 1,
|
||||
potentySinister: 1,
|
||||
nebuly: 2,
|
||||
seaWaves: 1,
|
||||
dragonTeeth: 1,
|
||||
firTrees: 1,
|
||||
};
|
||||
57
src/modules/emblem/lines.ts
Normal file
57
src/modules/emblem/lines.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
export const lines = {
|
||||
straight: "m 0,100 v15 h 200 v -15 z",
|
||||
engrailed:
|
||||
"m 0,95 a 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 v 20 H 0 Z",
|
||||
invecked:
|
||||
"M0,102.5 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 v12.5 H0 z",
|
||||
embattled:
|
||||
"M 0,105 H 2.5 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 2.5 v 10 H 0 Z",
|
||||
wavy: "m 200,115 v -15 c -8.9,3.5 -16,3.1 -25,0 -8.9,-3.5 -16,-3.1 -25,0 -8.9,3.5 -16,3.2 -25,0 -8.9,-3.5 -16,-3.2 -25,0 -8.9,3.5 -16,3.1 -25,0 -8.9,-3.5 -16,-3.1 -25,0 -8.9,3.5 -16,3.2 -25,0 -8.9,-3.5 -16,-3.2 -25,0 v 15 z",
|
||||
raguly:
|
||||
"m 200,95 h -3 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 H 97 l -5,10 H 82 L 87,95 H 77 l -5,10 H 62 L 67,95 H 57 l -5,10 H 42 L 47,95 H 37 l -5,10 H 22 L 27,95 H 17 l -5,10 H 2 L 7,95 H 0 v 20 h 200 z",
|
||||
dancetty:
|
||||
"m 0,105 10,-15 15,20 15,-20 15,20 15,-20 15,20 15,-20 15,20 15,-20 15,20 15,-20 15,20 15,-20 10,15 v 10 H 0 Z",
|
||||
dentilly:
|
||||
"M 180,105 170,95 v 10 L 160,95 v 10 L 150,95 v 10 L 140,95 v 10 L 130,95 v 10 L 120,95 v 10 L 110,95 v 10 L 100,95 v 10 L 90,95 v 10 L 80,95 v 10 L 70,95 v 10 L 60,95 v 10 L 50,95 v 10 L 40,95 v 10 L 30,95 v 10 L 20,95 v 10 L 10,95 v 10 L 0,95 v 20 H 200 V 105 L 190,95 v 10 L 180,95 Z",
|
||||
angled: "m 0,95 h 100 v 10 h 100 v 10 H 0 Z",
|
||||
urdy: "m 200,90 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,6 -5,-6 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,6 -5,-6 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 L 0,90 v 25 h 200",
|
||||
indented:
|
||||
"m 100,95 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 v 20 H 0 V 95 l 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 z",
|
||||
bevilled: "m 0,92.5 h 110 l -20,15 H 200 V 115 H 0 Z",
|
||||
nowy: "m 0,95 h 80 c 0,0 0.1,20.1 20,20 19.9,-0.1 20,-20 20,-20 h 80 v 20 H 0 Z",
|
||||
nowyReversed:
|
||||
"m 200,105 h -80 c 0,0 -0.1,-20.1 -20,-20 -19.9,0.1 -20,20 -20,20 H 0 v 10 h 200 z",
|
||||
potenty:
|
||||
"m 3,95 v 5 h 5 v 5 H 0 v 10 h 200 l 0.5,-10 H 193 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 H 100.5 93 v -5 h 5 V 95 H 83 v 5 h 5 v 5 H 73 v -5 h 5 V 95 H 63 v 5 h 5 v 5 H 53 v -5 h 5 V 95 H 43 v 5 h 5 v 5 H 33 v -5 h 5 V 95 H 23 v 5 h 5 v 5 H 13 v -5 h 5 v -5 z",
|
||||
potentyDexter:
|
||||
"m 200,105 h -2 v -10 0 0 h -10 v 5 h 5 v 5 H 183 V 95 h -10 v 5 h 5 v 5 H 168 V 95 h -10 v 5 h 5 v 5 H 153 V 95 h -10 v 5 h 5 v 5 H 138 V 95 h -10 v 5 h 5 v 5 H 123 V 95 h -10 v 5 h 5 v 5 h -10 v 0 0 -10 H 98 v 5 h 5 v 5 H 93 V 95 H 83 v 5 h 5 v 5 H 78 V 95 H 68 v 5 h 5 v 5 H 63 V 95 H 53 v 5 h 5 v 5 H 48 V 95 H 38 v 5 h 5 v 5 H 33 V 95 H 23 v 5 h 5 v 5 H 18 V 95 H 8 v 5 h 5 v 5 H 3 V 95 H 0 v 20 h 200 z",
|
||||
potentySinister:
|
||||
"m 2.5,95 v 10 H 0 v 10 h 202.5 v -15 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 z",
|
||||
embattledGhibellin:
|
||||
"M 200,200 V 100 l -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 v 15 h 200",
|
||||
embattledNotched:
|
||||
"m 200,105 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 H 90 V 95 l -5,5 -5,-5 v 10 H 75 V 95 l -5,5 -5,-5 v 10 H 60 V 95 l -5,5 -5,-5 v 10 H 45 V 95 l -5,5 -5,-5 v 10 H 30 V 95 l -5,5 -5,-5 v 10 H 15 V 95 l -5,5 -5,-5 v 10 H 0 v 10 h 200",
|
||||
embattledGrady:
|
||||
"m 0,95 v 20 H 200 V 95 h -2.5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 z",
|
||||
dovetailed:
|
||||
"m 200,95 h -7 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 H 93 l 4,10 H 83 L 87,95 H 73 l 4,10 H 63 L 67,95 H 53 l 4,10 H 43 L 47,95 H 33 l 4,10 H 23 L 27,95 H 13 l 4,10 H 3 L 7,95 H 0 v 20 h 200",
|
||||
dovetailedIndented:
|
||||
"m 200,100 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 v 15 h 200",
|
||||
nebuly:
|
||||
"m 13.1,89.8 c -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.2,4.5 -7.3,4.5 -0.5,0 -2.2,-0.2 -2.2,-0.2 V 115 h 200 v -10.1 c -3.7,-0.2 -6.7,-2.2 -6.7,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.8,-1.9 1.8,-3.1 0,-2.5 -3.2,-4.5 -7.2,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.8,-1.9 1.8,-3.1 0,-2.5 -3.2,-4.5 -7.2,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 -1.5,4.1 -4.2,4.4 -8.8,4.5 -4.7,-0.1 -8.7,-1.5 -8.9,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 z",
|
||||
rayonne:
|
||||
"M0 115l-.1-6 .2.8c1.3-1 2.3-2.5 2.9-4.4.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4A9 9 0 015.5 90c-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 2.1 3.1 3.1 4.6 1 1.6 2.4 3.1 2.7 4.8.3 1.7.3 3.3 0 5.2 1.3-1 2.6-2.7 3.2-4.6.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.75 2.79 2.72 4.08 4.45 5.82L200 115z",
|
||||
seaWaves:
|
||||
"m 28.83,94.9 c -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.44,-3.6 3.6,-3.6 0.7,0 1.36,0.17 1.93,0.48 -0.33,-2.03 -2.19,-3.56 -4.45,-3.56 -4.24,0 -6.91,3.13 -8.5,5.13 V 115 h 200 v -14.89 c -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.44,-3.6 3.6,-3.6 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -6.6,3.09 -8.19,5.09 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 z",
|
||||
dragonTeeth:
|
||||
"M 9.4,85 C 6.5,88.1 4.1,92.9 3,98.8 1.9,104.6 2.3,110.4 3.8,115 2.4,113.5 0,106.6 0,109.3 v 5.7 h 200 v -5.7 c -1.1,-2.4 -2,-5.1 -2.6,-8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -1.4,-1.5 -2.8,-3.9 -3.8,-6.1 -1.1,-2.4 -2.3,-6.1 -2.6,-7.7 -0.2,-5.9 0.2,-11.7 1.7,-16.3 -3,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1,-5.8 -0.7,-11.6 0.9,-16.2 -3,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.9,-16.2 -3,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 C 63,95.4 63.4,89.6 64.9,85 c -2.9,3.1 -5.3,7.9 -6.3,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1,5.8 -0.6,11.6 0.9,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1,5.8 -0.7,11.6 0.9,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.9,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 C 18.6,95.4 19,89.6 20.5,85 17.6,88.1 15.2,92.9 14.1,98.8 13,104.6 13.4,110.4 14.9,115 12,111.9 9.6,107.1 8.6,101.2 7.5,95.4 7.9,89.6 9.4,85 Z",
|
||||
firTrees:
|
||||
"m 3.9,90 -4,7 2,-0.5 L 0,100 v 15 h 200 v -15 l -1.9,-3.5 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4.1,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4.1,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 z",
|
||||
flechy: "m 0,100 h 85 l 15,-15 15,15 h 85 v 15 H 0 Z",
|
||||
barby: "m 0,100 h 85 l 15,15 15,-15 h 85 v 15 H 0 Z",
|
||||
enclavy: "M 0,100 H 85 V 85 h 30 v 15 h 85 v 15 H 0 Z",
|
||||
escartely: "m 0,100 h 85 v 15 h 30 v -15 h 85 v 15 H 0 Z",
|
||||
arched: "m 100,95 c 40,-0.2 100,20 100,20 H 0 c 0,0 60,-19.8 100,-20 z",
|
||||
archedReversed:
|
||||
"m 0,85 c 0,0 60,20.2 100,20 40,-0.2 100,-20 100,-20 v 30 H 0 Z",
|
||||
};
|
||||
162
src/modules/emblem/ordinaries.ts
Normal file
162
src/modules/emblem/ordinaries.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
export const ordinaries = {
|
||||
lined: {
|
||||
pale: 7,
|
||||
fess: 5,
|
||||
bend: 3,
|
||||
bendSinister: 2,
|
||||
chief: 5,
|
||||
bar: 2,
|
||||
gemelle: 1,
|
||||
fessCotissed: 1,
|
||||
fessDoubleCotissed: 1,
|
||||
bendlet: 2,
|
||||
bendletSinister: 1,
|
||||
terrace: 3,
|
||||
cross: 6,
|
||||
crossParted: 1,
|
||||
saltire: 2,
|
||||
saltireParted: 1,
|
||||
},
|
||||
straight: {
|
||||
bordure: 8,
|
||||
orle: 4,
|
||||
mount: 1,
|
||||
point: 2,
|
||||
flaunches: 1,
|
||||
gore: 1,
|
||||
gyron: 1,
|
||||
quarter: 1,
|
||||
canton: 2,
|
||||
pall: 3,
|
||||
pallReversed: 2,
|
||||
chevron: 4,
|
||||
chevronReversed: 3,
|
||||
pile: 2,
|
||||
pileInBend: 2,
|
||||
pileInBendSinister: 1,
|
||||
piles: 1,
|
||||
pilesInPoint: 2,
|
||||
label: 1,
|
||||
},
|
||||
data: {
|
||||
bar: {
|
||||
positionsOn: { defdefdef: 1 },
|
||||
positionsOff: { abc: 2, abcgzi: 1, jlh: 5, bgi: 2, ach: 1 },
|
||||
},
|
||||
bend: {
|
||||
positionsOn: { ee: 2, jo: 1, joe: 1 },
|
||||
positionsOff: { ccg: 2, ccc: 1 },
|
||||
},
|
||||
bendSinister: {
|
||||
positionsOn: { ee: 1, lm: 1, lem: 4 },
|
||||
positionsOff: { aai: 2, aaa: 1 },
|
||||
},
|
||||
bendlet: {
|
||||
positionsOn: { joejoejoe: 1 },
|
||||
positionsOff: { ccg: 2, ccc: 1 },
|
||||
},
|
||||
bendletSinister: {
|
||||
positionsOn: { lemlemlem: 1 },
|
||||
positionsOff: { aai: 2, aaa: 1 },
|
||||
},
|
||||
bordure: {
|
||||
positionsOn: { ABCDEFGHIJKL: 1 },
|
||||
positionsOff: { e: 4, jleh: 2, kenken: 1, peqpeq: 1 },
|
||||
},
|
||||
canton: {
|
||||
positionsOn: { yyyy: 1 },
|
||||
positionsOff: { e: 5, beh: 1, def: 1, bdefh: 1, kn: 1 },
|
||||
},
|
||||
chevron: {
|
||||
positionsOn: { ach: 3, hhh: 1 },
|
||||
},
|
||||
chevronReversed: {
|
||||
positionsOff: { bbb: 1 },
|
||||
},
|
||||
chief: {
|
||||
positionsOn: { abc: 5, bbb: 1 },
|
||||
positionsOff: { emo: 2, emoz: 1, ez: 2 },
|
||||
},
|
||||
cross: {
|
||||
positionsOn: { eeee: 1, behdfbehdf: 3, behbehbeh: 2 },
|
||||
positionsOff: { acgi: 1 },
|
||||
},
|
||||
crossParted: {
|
||||
positionsOn: { e: 5, ee: 1 },
|
||||
},
|
||||
fess: {
|
||||
positionsOn: { ee: 1, def: 3 },
|
||||
positionsOff: { abc: 3, abcz: 1 },
|
||||
},
|
||||
fessCotissed: {
|
||||
positionsOn: { ee: 1, def: 3 },
|
||||
},
|
||||
fessDoubleCotissed: {
|
||||
positionsOn: { ee: 1, defdef: 3 },
|
||||
},
|
||||
flaunches: {
|
||||
positionsOff: { e: 3, kn: 1, beh: 3 },
|
||||
},
|
||||
gemelle: {
|
||||
positionsOff: { abc: 1 },
|
||||
},
|
||||
gyron: {
|
||||
positionsOff: { bh: 1 },
|
||||
},
|
||||
label: {
|
||||
positionsOff: { defgzi: 2, eh: 3, defdefhmo: 1, egiegi: 1, pqn: 5 },
|
||||
},
|
||||
mount: {
|
||||
positionsOff: { e: 5, def: 1, bdf: 3 },
|
||||
},
|
||||
orle: {
|
||||
positionsOff: { e: 4, jleh: 1, kenken: 1, peqpeq: 1 },
|
||||
},
|
||||
pale: {
|
||||
positionsOn: { ee: 12, beh: 10, kn: 3, bb: 1 },
|
||||
positionsOff: { yyy: 1 },
|
||||
},
|
||||
pall: {
|
||||
positionsOn: { ee: 1, jleh: 5, jlhh: 3 },
|
||||
positionsOff: { BCKFEILGJbdmfo: 1 },
|
||||
},
|
||||
pallReversed: {
|
||||
positionsOn: { ee: 1, bemo: 5 },
|
||||
positionsOff: { aczac: 1 },
|
||||
},
|
||||
pile: {
|
||||
positionsOn: { bbb: 1 },
|
||||
positionsOff: { acdfgi: 1, acac: 1 },
|
||||
},
|
||||
pileInBend: {
|
||||
positionsOn: { eeee: 1, eeoo: 1 },
|
||||
positionsOff: { cg: 1 },
|
||||
},
|
||||
pileInBendSinister: {
|
||||
positionsOn: { eeee: 1, eemm: 1 },
|
||||
positionsOff: { ai: 1 },
|
||||
},
|
||||
point: {
|
||||
positionsOff: { e: 2, def: 1, bdf: 3, acbdef: 1 },
|
||||
},
|
||||
quarter: {
|
||||
positionsOn: { jjj: 1 },
|
||||
positionsOff: { e: 1 },
|
||||
},
|
||||
saltire: {
|
||||
positionsOn: { ee: 5, jlemo: 1 },
|
||||
},
|
||||
saltireParted: {
|
||||
positionsOn: { e: 5, ee: 1 },
|
||||
},
|
||||
terrace: {
|
||||
positionsOff: { e: 5, def: 1, bdf: 3 },
|
||||
},
|
||||
} as Record<
|
||||
string,
|
||||
{
|
||||
positionsOn?: Record<string, number>;
|
||||
positionsOff?: Record<string, number>;
|
||||
}
|
||||
>,
|
||||
};
|
||||
70
src/modules/emblem/paths.ts
Normal file
70
src/modules/emblem/paths.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
export const shieldPaths = {
|
||||
heater: "m25,25 h150 v50 a150,150,0,0,1,-75,125 a150,150,0,0,1,-75,-125 z",
|
||||
spanish: "m25,25 h150 v100 a75,75,0,0,1,-150,0 z",
|
||||
french:
|
||||
"m 25,25 h 150 v 139.15 c 0,41.745 -66,18.15 -75,36.3 -9,-18.15 -75,5.445 -75,-36.3 v 0 z",
|
||||
horsehead:
|
||||
"m 20,40 c 0,60 40,80 40,100 0,10 -4,15 -0.35,30 C 65,185.7 81,200 100,200 c 19.1,0 35.3,-14.6 40.5,-30.4 C 144.2,155 140,150 140,140 140,120 180,100 180,40 142.72,40 150,15 100,15 55,15 55,40 20,40 Z",
|
||||
horsehead2:
|
||||
"M60 20c-5 20-10 35-35 55 25 35 35 65 30 100 20 0 35 10 45 26 10-16 30-26 45-26-5-35 5-65 30-100a87 87 0 01-35-55c-25 3-55 3-80 0z",
|
||||
polish:
|
||||
"m 90.3,6.3 c -12.7,0 -20.7,10.9 -40.5,14 0,11.8 -4.9,23.5 -11.4,31.1 0,0 12.7,6 12.7,19.3 C 51.1,90.8 30,90.8 30,90.8 c 0,0 -3.6,7.4 -3.6,22.4 0,34.3 23.1,60.2 40.7,68.2 17.6,8 27.7,11.4 32.9,18.6 5.2,-7.3 15.3,-10.7 32.8,-18.6 17.6,-8 40.7,-33.9 40.7,-68.2 0,-15 -3.6,-22.4 -3.6,-22.4 0,0 -21.1,0 -21.1,-20.1 0,-13.3 12.7,-19.3 12.7,-19.3 C 155.1,43.7 150.2,32.1 150.2,20.3 130.4,17.2 122.5,6.3 109.7,6.3 102.5,6.3 100,10 100,10 c 0,0 -2.5,-3.7 -9.7,-3.7 z",
|
||||
hessen:
|
||||
"M170 20c4 5 8 13 15 20 0 0-10 0-10 15 0 100-15 140-75 145-65-5-75-45-75-145 0-15-10-15-10-15l15-20c0 15 10-5 70-5s70 20 70 5z",
|
||||
swiss:
|
||||
"m 25,20 c -0.1,0 25.2,8.5 37.6,8.5 C 75.1,28.5 99.1,20 100,20 c 0.6,0 24.9,8.5 37.3,8.5 C 149.8,28.5 174.4,20 175,20 l -0.3,22.6 C 173.2,160.3 100,200 100,200 100,200 26.5,160.9 25.2,42.6 Z",
|
||||
boeotian:
|
||||
"M150 115c-5 0-10-5-10-15s5-15 10-15c10 0 7 10 15 10 10 0 0-30 0-30-10-25-30-55-65-55S45 40 35 65c0 0-10 30 0 30 8 0 5-10 15-10 5 0 10 5 10 15s-5 15-10 15c-10 0-7-10-15-10-10 0 0 30 0 30 10 25 30 55 65 55s55-30 65-55c0 0 10-30 0-30-8 0-5 10-15 10z",
|
||||
roman: "m 160,170 c -40,20 -80,20 -120,0 V 30 C 80,10 120,10 160,30 Z",
|
||||
kite: "m 53.3,46.4 c 0,4.1 1,12.3 1,12.3 7.1,55.7 45.7,141.3 45.7,141.3 0,0 38.6,-85.6 45.7,-141.2 0,0 1,-8.1 1,-12.3 C 146.7,20.9 125.8,0.1 100,0.1 74.2,0.1 53.3,20.9 53.3,46.4 Z",
|
||||
oldFrench: "m25,25 h150 v75 a100,100,0,0,1,-75,100 a100,100,0,0,1,-75,-100 z",
|
||||
renaissance:
|
||||
"M 25,33.9 C 33.4,50.3 36.2,72.9 36.2,81.7 36.2,109.9 25,122.6 25,141 c 0,29.4 24.9,44.1 40.2,47.7 15.3,3.7 29.3,0 34.8,11.3 5.5,-11.3 19.6,-7.6 34.8,-11.3 C 150.1,185 175,170.3 175,141 c 0,-18.4 -11.2,-31.1 -11.2,-59.3 0,-8.8 2.8,-31.3 11.2,-47.7 L 155.7,14.4 C 138.2,21.8 119.3,25.7 100,25.7 c -19.3,0 -38.2,-3.9 -55.7,-11.3 z",
|
||||
baroque:
|
||||
"m 100,25 c 18,0 50,2 75,14 v 37 l -2.7,3.2 c -4.9,5.4 -6.6,9.6 -6.7,16.2 0,6.5 2,11.6 6.9,17.2 l 2.8,3.1 v 10.2 c 0,17.7 -2.2,27.7 -7.8,35.9 -5,7.3 -11.7,11.3 -32.3,19.4 -12.6,5 -20.2,8.8 -28.6,14.5 C 103.3,198 100,200 100,200 c 0,0 -2.8,-2.3 -6.4,-4.7 C 85.6,189.8 78,186 65,180.9 32.4,168.1 26.9,160.9 25.8,129.3 L 25,116 l 3.3,-3.3 c 4.8,-5.2 7,-10.7 7,-17.3 0,-6.8 -1.8,-11.1 -6.5,-16.1 L 25,76 V 39 C 50,27 82,25 100,25 Z",
|
||||
targe:
|
||||
"m 20,35 c 15,0 115,-60 155,-10 -5,10 -15,15 -10,50 5,45 10,70 -10,90 C 125,195 75,195 50,175 25,150 30,130 35,85 50,95 65,85 65,70 65,50 50,45 40,50 30,55 27,65 30,70 23,73 20,70 14,70 11,60 20,45 20,35 Z",
|
||||
targe2:
|
||||
"m 84,32.2 c 6.2,-1 19.5,-31.4 94.1,-20.2 -30.57,33.64 -21.66,67.37 -11.2,95 20.2,69.5 -41.17549,84.7 -66.88,84.7 C 74.32,191.7071 8.38,168.95 32,105.9 36.88,92.88 31,89 31,82.6 35.15,82.262199 56.79,86.17 56.5,69.8 56.20,52.74 42.2,47.9 25.9,55.2 25.9,51.4 39.8,6.7 84,32.2 Z",
|
||||
pavise:
|
||||
"M95 7L39.9 37.3a10 10 0 00-5.1 9.5L46 180c.4 5.2 3.7 10 9 10h90c5.3 0 9.6-4.8 10-10l10.6-133.2a10 10 0 00-5-9.5L105 7c-4.2-2.3-6.2-2.3-10 0z",
|
||||
wedged:
|
||||
"m 51.2,19 h 96.4 c 3.1,12.7 10.7,20.9 26.5,20.8 C 175.7,94.5 165.3,144.3 100,200 43.5,154.2 22.8,102.8 25.1,39.7 37,38.9 47.1,34.7 51.2,19 Z",
|
||||
round:
|
||||
"m 185,100 a 85,85 0 0 1 -85,85 85,85 0 0 1 -85,-85 85,85 0 0 1 85,-85 85,85 0 0 1 85,85",
|
||||
oval: "m 32.3,99.5 a 67.7,93.7 0 1 1 0,1.3 z",
|
||||
vesicaPiscis:
|
||||
"M 100,0 C 63.9,20.4 41,58.5 41,100 c 0,41.5 22.9,79.6 59,100 36.1,-20.4 59,-58.5 59,-100 C 159,58.5 136.1,20.4 100,0 Z",
|
||||
square: "M 25,25 H 175 V 175 H 25 Z",
|
||||
diamond: "M 25,100 100,200 175,100 100,0 Z",
|
||||
no: "m0,0 h200 v200 h-200 z",
|
||||
flag: "M 10,40 h180 v120 h-180 Z",
|
||||
pennon: "M 10,40 l190,60 -190,60 Z",
|
||||
guidon: "M 10,40 h190 l-65,60 65,60 h-190 Z",
|
||||
banner: "m 25,25 v 170 l 25,-40 25,40 25,-40 25,40 25,-40 25,40 V 25 Z",
|
||||
dovetail: "m 25,25 v 175 l 75,-40 75,40 V 25 Z",
|
||||
gonfalon: "m 25,25 v 125 l 75,50 75,-50 V 25 Z",
|
||||
pennant: "M 25,15 100,200 175,15 Z",
|
||||
fantasy1:
|
||||
"M 100,5 C 85,30 40,35 15,40 c 40,35 20,90 40,115 15,25 40,30 45,45 5,-15 30,-20 45,-45 20,-25 0,-80 40,-115 C 160,35 115,30 100,5 Z",
|
||||
fantasy2:
|
||||
"m 152,21 c 0,0 -27,14 -52,-4 C 75,35 48,21 48,21 50,45 30,55 30,75 60,75 60,115 32,120 c 3,40 53,50 68,80 15,-30 65,-40 68,-80 -28,-5 -28,-45 2,-45 C 170,55 150,45 152,21 Z",
|
||||
fantasy3:
|
||||
"M 167,67 C 165,0 35,0 33,67 c 32,-7 27,53 -3,43 -5,45 60,65 70,90 10,-25 75,-47.51058 70,-90 -30,10 -35,-50 -3,-43 z",
|
||||
fantasy4:
|
||||
"M100 9C55 48 27 27 13 39c23 50 3 119 49 150 14 9 28 11 38 11s27-4 38-11c55-39 24-108 49-150-14-12-45 7-87-30z",
|
||||
fantasy5:
|
||||
"M 100,0 C 75,25 30,25 30,25 c 0,69 20,145 70,175 50,-30 71,-106 70,-175 0,0 -45,0 -70,-25 z",
|
||||
noldor:
|
||||
"m 55,75 h 2 c 3,-25 38,-10 3,20 15,50 30,75 40,105 10,-30 25,-55 40,-105 -35,-30 0,-45 3,-20 h 2 C 150,30 110,20 100,0 90,20 50,30 55,75 Z",
|
||||
gondor:
|
||||
"m 100,200 c 15,-15 38,-35 45,-60 h 5 V 30 h -5 C 133,10 67,10 55,30 h -5 v 110 h 5 c 7,25 30,45 45,60 z",
|
||||
easterling: "M 160,185 C 120,170 80,170 40,185 V 15 c 40,15 80,15 120,0 z",
|
||||
erebor:
|
||||
"M25 135 V60 l22-13 16-37 h75 l15 37 22 13 v75l-22 18-16 37 H63l-16-37z",
|
||||
ironHills: "m 30,25 60,-10 10,10 10,-10 60,10 -5,125 -65,50 -65,-50 z",
|
||||
urukHai:
|
||||
"M 30,60 C 40,60 60,50 60,20 l -5,-3 45,-17 75,40 -5,5 -35,155 -5,-35 H 70 v 35 z",
|
||||
moriaOrc:
|
||||
"M45 35c5 3 7 10 13 9h19c4-2 7-4 9-9 6 1 9 9 16 11 7-2 14 0 21 0 6-3 6-10 10-15 2-5 1-10-2-15-2-4-5-14-4-16 3 6 7 11 12 14 7 3 3 12 7 16 3 6 4 12 9 18 2 4 6 8 5 14 0 6-1 12 3 18-3 6-2 13-1 20 1 6-2 12-1 18 0 6-3 13 0 18 8 4 0 8-5 7-4 3-9 3-13 9-5 5-5 13-8 19 0 6 0 15-7 16-1 6-7 6-10 12-1-6 0-6-2-9l2-19c2-4 5-12-3-12-4-5-11-5-15 1l-13-18c-3-4-2 9-3 12 2 2-4-6-7-5-8-2-8 7-11 11-2 4-5 10-8 9 3-10 3-16 1-23-1-4 2-9-4-11 0-6 1-13-2-19-4-2-9-6-13-7V91c4-7-5-13 0-19-3-7 2-11 2-18-1-6 1-12 3-17v-1z",
|
||||
};
|
||||
126
src/modules/emblem/patterns.ts
Normal file
126
src/modules/emblem/patterns.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
export const patterns = {
|
||||
semy: (p: string, c1: string, c2: string, size: number, chargeId: string) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="#000"><rect width="200" height="200" fill="${c1}" stroke="none"/><g fill="${c2}"><use transform="translate(-100 -50)" href="#${chargeId}"/><use transform="translate(100 -50)" href="#${chargeId}"/><use transform="translate(0 50)" href="#${chargeId}"/></g></pattern>`,
|
||||
vair: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 25 50" stroke="#000" stroke-width=".2"><rect width="25" height="25" fill="${c1}" stroke="none"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}"/><rect x="0" y="25" width="25" height="25" fill="${c2}" stroke="none"/><path d="m25,25 l-6.25,6.25 v12.5 l-6.25,6.25 l-6.25,-6.25 v-12.5 l-6.25,-6.25 z" fill="${c1}"/><path d="M0 50 h25" fill="none"/></pattern>`,
|
||||
counterVair: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 25 50" stroke="#000" stroke-width=".2"><rect width="25" height="50" fill="${c2}" stroke="none"/><path d="m 12.5,0 6.25,6.25 v 12.5 L 25,25 18.75,31.25 v 12.5 L 12.5,50 6.25,43.75 V 31.25 L 0,25 6.25,18.75 V 6.25 Z" fill="${c1}"/></pattern>`,
|
||||
vairInPale: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 25 25"><rect width="25" height="25" fill="${c1}"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}" stroke="#000" stroke-width=".2"/></pattern>`,
|
||||
vairEnPointe: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 25 50"><rect width="25" height="25" fill="${c2}"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c1}"/><rect x="0" y="25" width="25" height="25" fill="${c1}" stroke-width="1" stroke="${c1}"/><path d="m12.5,25 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}"/></pattern>`,
|
||||
vairAncien: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><path fill="${c2}" stroke="none" d="m 0,90 c 10,0 25,-5 25,-40 0,-25 10,-40 25,-40 15,0 25,15 25,40 0,35 15,40 25,40 v 10 H 0 Z"/><path fill="none" stroke="#000" d="M 0,90 c 10,0 25,-5 25,-40 0,-35 15,-40 25,-40 10,0 25,5 25,40 0,35 15,40 25,40 M0,100 h100"/></pattern>`,
|
||||
potent: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="#000"><rect width="200" height="100" fill="${c1}" stroke="none"/><rect y="100" width="200" height="100" fill="${c2}" stroke="none"/><path d="m25 50h50v-50h50v50h50v50h-150z" fill="${c2}"/><path d="m25 100v50h50v50h50v-50h50v-50z" fill="${c1}"/><path d="m0 0h200 M0 100h200" fill="none"/></pattern>`,
|
||||
counterPotent: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="none"><rect width="200" height="200" fill="${c1}"/><path d="m25 50h50v-50h50v50h50v100h-50v50h-50v-50h-50v-50z" fill="${c2}"/><path d="m0 0h200 M0 100h200 M0 200h200"/></pattern>`,
|
||||
potentInPale: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.0625
|
||||
}" viewBox="0 0 200 100" stroke-width="1"><rect width="200" height="100" fill="${c1}" stroke="none"/><path d="m25 50h50v-50h50v50h50v50h-150z" fill="${c2}" stroke="#000"/><path d="m0 0h200 M0 100h200" fill="none" stroke="#000"/></pattern>`,
|
||||
potentEnPointe: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="none"><rect width="200" height="200" fill="${c1}"/><path d="m0 0h25v50h50v50h50v-50h50v-50h25v100h-25v50h-50v50h-50v-50h-50v-50h-25v-100" fill="${c2}"/></pattern>`,
|
||||
ermine: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" fill="${c2}"><rect width="200" height="200" fill="${c1}"/><g stroke="none" fill="${c2}"><g transform="translate(-100 -50)"><path d="m100 81.1c-4.25 17.6-12.7 29.8-21.2 38.9 3.65-0.607 7.9-3.04 11.5-5.47-2.42 4.86-4.86 8.51-7.3 12.7 1.82-0.607 6.07-4.86 12.7-10.9 1.21 8.51 2.42 17.6 4.25 23.6 1.82-5.47 3.04-15.2 4.25-23.6 3.65 3.65 7.3 7.9 12.7 10.9l-7.9-13.3c3.65 1.82 7.9 4.86 11.5 6.07-9.11-9.11-17-21.2-20.6-38.9z"/><path d="m82.4 81.7c-0.607-0.607-6.07 2.42-9.72-4.25 7.9 6.68 15.2-7.3 21.8 1.82 1.82 4.25-6.68 10.9-12.1 2.42z"/><path d="m117 81.7c0.607-1.21 6.07 2.42 9.11-4.86-7.3 7.3-15.2-7.3-21.2 2.42-1.82 4.25 6.68 10.9 12.1 2.42z"/><path d="m101 66.5c-1.02-0.607 3.58-4.25-3.07-8.51 5.63 7.9-10.2 10.9-1.54 17.6 3.58 2.42 12.2-2.42 4.6-9.11z"/></g><g transform="translate(100 -50)"><path d="m100 81.1c-4.25 17.6-12.7 29.8-21.2 38.9 3.65-0.607 7.9-3.04 11.5-5.47-2.42 4.86-4.86 8.51-7.3 12.7 1.82-0.607 6.07-4.86 12.7-10.9 1.21 8.51 2.42 17.6 4.25 23.6 1.82-5.47 3.04-15.2 4.25-23.6 3.65 3.65 7.3 7.9 12.7 10.9l-7.9-13.3c3.65 1.82 7.9 4.86 11.5 6.07-9.11-9.11-17-21.2-20.6-38.9z"/><path d="m82.4 81.7c-0.607-0.607-6.07 2.42-9.72-4.25 7.9 6.68 15.2-7.3 21.8 1.82 1.82 4.25-6.68 10.9-12.1 2.42z"/><path d="m117 81.7c0.607-1.21 6.07 2.42 9.11-4.86-7.3 7.3-15.2-7.3-21.2 2.42-1.82 4.25 6.68 10.9 12.1 2.42z"/><path d="m101 66.5c-1.02-0.607 3.58-4.25-3.07-8.51 5.63 7.9-10.2 10.9-1.54 17.6 3.58 2.42 12.2-2.42 4.6-9.11z"/></g><g transform="translate(0 50)"><path d="m100 81.1c-4.25 17.6-12.7 29.8-21.2 38.9 3.65-0.607 7.9-3.04 11.5-5.47-2.42 4.86-4.86 8.51-7.3 12.7 1.82-0.607 6.07-4.86 12.7-10.9 1.21 8.51 2.42 17.6 4.25 23.6 1.82-5.47 3.04-15.2 4.25-23.6 3.65 3.65 7.3 7.9 12.7 10.9l-7.9-13.3c3.65 1.82 7.9 4.86 11.5 6.07-9.11-9.11-17-21.2-20.6-38.9z"/><path d="m82.4 81.7c-0.607-0.607-6.07 2.42-9.72-4.25 7.9 6.68 15.2-7.3 21.8 1.82 1.82 4.25-6.68 10.9-12.1 2.42z"/><path d="m117 81.7c0.607-1.21 6.07 2.42 9.11-4.86-7.3 7.3-15.2-7.3-21.2 2.42-1.82 4.25 6.68 10.9 12.1 2.42z"/><path d="m101 66.5c-1.02-0.607 3.58-4.25-3.07-8.51 5.63 7.9-10.2 10.9-1.54 17.6 3.58 2.42 12.2-2.42 4.6-9.11z"/></g></g></pattern>`,
|
||||
chequy: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.25}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 50 50" fill="${c2}"><rect width="50" height="50"/><rect width="25" height="25" fill="${c1}"/><rect x="25" y="25" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
lozengy: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 50 50"><rect width="50" height="50" fill="${c1}"/><polygon points="25,0 50,25 25,50 0,25" fill="${c2}"/></pattern>`,
|
||||
fusily: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 50 100"><rect width="50" height="100" fill="${c2}"/><polygon points="25,0 50,50 25,100 0,50" fill="${c1}"/></pattern>`,
|
||||
pally: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.5}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 25"><rect width="100" height="25" fill="${c2}"/><rect x="25" y="0" width="25" height="25" fill="${c1}"/><rect x="75" y="0" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
barry: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.5
|
||||
}" viewBox="0 0 25 100"><rect width="25" height="100" fill="${c2}"/><rect x="0" y="25" width="25" height="25" fill="${c1}"/><rect x="0" y="75" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
gemelles: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 50 50"><rect width="50" height="50" fill="${c1}"/><rect y="5" width="50" height="10" fill="${c2}"/><rect y="40" width="50" height="10" fill="${c2}"/></pattern>`,
|
||||
bendy: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.5}" height="${
|
||||
size * 0.5
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><polygon points="0,25 75,100 25,100 0,75" fill="${c2}"/><polygon points="25,0 75,0 100,25 100,75" fill="${c2}"/></pattern>`,
|
||||
bendySinister: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.5}" height="${
|
||||
size * 0.5
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c2}"/><polygon points="0,25 25,0 75,0 0,75" fill="${c1}"/><polygon points="25,100 100,25 100,75 75,100" fill="${c1}"/></pattern>`,
|
||||
palyBendy: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.6258}" height="${
|
||||
size * 0.3576
|
||||
}" viewBox="0 0 175 100"><rect y="0" x="0" width="175" height="100" fill="${c2}"/><g fill="${c1}"><path d="m0 20 35 30v50l-35-30z"/><path d="m35 0 35 30v50l-35-30z"/><path d="m70 0h23l12 10v50l-35-30z"/><path d="m70 80 23 20h-23z"/><path d="m105 60 35 30v10h-35z"/><path d="m105 0h35v40l-35-30z"/><path d="m 140,40 35,30 v 30 h -23 l -12,-10z"/><path d="M 175,0 V 20 L 152,0 Z"/></g></pattern>`,
|
||||
barryBendy: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.3572}" height="${
|
||||
size * 0.6251
|
||||
}" viewBox="0 0 100 175"><rect width="100" height="175" fill="${c2}"/><g fill="${c1}"><path d="m20 0 30 35h50l-30-35z"/><path d="m0 35 30 35h50l-30-35z"/><path d="m0 70v23l10 12h50l-30-35z"/><path d="m80 70 20 23v-23z"/><path d="m60 105 30 35h10v-35z"/><path d="m0 105v35h40l-30-35z"/><path d="m 40,140 30,35 h 30 v -23 l -10,-12 z"/><path d="m0 175h20l-20-23z"/></g></pattern>`,
|
||||
pappellony: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><circle cx="0" cy="51" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/><circle cx="100" cy="51" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/><circle cx="50" cy="1" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/></pattern>`,
|
||||
pappellony2: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100" stroke="#000" stroke-width="2"><rect width="100" height="100" fill="${c1}" stroke="none"/><circle cy="50" r="49" fill="${c2}"/><circle cx="100" cy="50" r="49" fill="${c2}"/><circle cx="50" cy="0" r="49" fill="${c1}"/></pattern>`,
|
||||
scaly: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100" stroke="#000"><rect width="100" height="100" fill="${c1}" stroke="none"/><path d="M 0,84 C -40,84 -50,49 -50,49 -50,79 -27,99 0,99 27,99 50,79 50,49 50,49 40,84 0,84 Z" fill="${c2}"/><path d="M 100,84 C 60,84 50,49 50,49 c 0,30 23,50 50,50 27,0 50,-20 50,-50 0,0 -10,35 -50,35 z" fill="${c2}"/><path d="M 50,35 C 10,35 0,0 0,0 0,30 23,50 50,50 77,50 100,30 100,0 100,0 90,35 50,35 Z" fill="${c2}"/></pattern>`,
|
||||
plumetty: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 50 100" stroke-width=".8"><rect width="50" height="100" fill="${c2}" stroke="none"/><path fill="${c1}" stroke="none" d="M 25,100 C 44,88 49.5,74 50,50 33.5,40 25,25 25,4e-7 25,25 16.5,40 0,50 0.5,74 6,88 25,100 Z"/><path fill="none" stroke="${c2}" d="m17 40c5.363 2.692 10.7 2.641 16 0m-19 7c7.448 4.105 14.78 3.894 22 0m-27 7c6-2 10.75 3.003 16 3 5.412-0.0031 10-5 16-3m-35 9c4-7 12 3 19 2 7 1 15-9 19-2m-35 6c6-2 11 3 16 3s10-5 16-3m-30 7c8 0 8 3 14 3s7-3 14-3m-25 8c7.385 4.048 14.72 3.951 22 0m-19 8c5.455 2.766 10.78 2.566 16 0m-8 6v-78"/><g fill="none" stroke="${c1}"><path d="m42 90c2.678 1.344 5.337 2.004 8 2m-11 5c3.686 2.032 7.344 3.006 10.97 3m0.0261-1.2e-4v-30"/><path d="m0 92c2.689 0.0045 5.328-0.6687 8-2m-8 10c3.709-0.0033 7.348-1.031 11-3m-11 3v-30"/><path d="m0 7c5.412-0.0031 10-5 16-3m-16 11c7 1 15-9 19-2m-19 9c5 0 10-5 16-3m-16 10c6 0 7-3 14-3m-14.02 11c3.685-0.002185 7.357-1.014 11.02-3m-11 10c2.694-0.01117 5.358-0.7036 7.996-2m-8 6v-48"/><path d="m34 4c6-2 10.75 3.003 16 3m-19 6c4-7 12 3 19 2m-16 4c6-2 11 3 16 3m-14 4c8 0 8 3 14 3m-11 5c3.641 1.996 7.383 2.985 11 3m-8 5c2.762 1.401 5.303 2.154 8.002 2.112m-0.00154 3.888v-48"/></g></pattern>`,
|
||||
masoned: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100" fill="none"><rect width="100" height="100" fill="${c1}"/><rect width="100" height="50" stroke="${c2}" stroke-width="4"/><line x1="50" y1="50" x2="50" y2="100" stroke="${c2}" stroke-width="5"/></pattern>`,
|
||||
fretty: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.2}" height="${
|
||||
size * 0.2
|
||||
}" viewBox="0 0 140 140" stroke="#000" stroke-width="2"><rect width="140" height="140" fill="${c1}" stroke="none"/><path d="m-15 5 150 150 20-20-150-150z" fill="${c2}"/><path d="m10 150 140-140-20-20-140 140z" fill="${c2}" stroke="none"/><path d="m0 120 20 20 120-120-20-20z" fill="none"/></pattern>`,
|
||||
grillage: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.25}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 200 200" stroke="#000" stroke-width="2"><rect width="200" height="200" fill="${c1}" stroke="none"/><path d="m205 65v-30h-210v30z" fill="${c2}"/><path d="m65-5h-30v210h30z" fill="${c2}"/><path d="m205 165v-30h-210v30z" fill="${c2}"/><path d="m165,65h-30v140h30z" fill="${c2}"/><path d="m 165,-5h-30v40h30z" fill="${c2}"/></pattern>`,
|
||||
chainy: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.167}" height="${
|
||||
size * 0.167
|
||||
}" viewBox="0 0 200 200" stroke="#000" stroke-width="2"><rect x="-6.691e-6" width="200" height="200" fill="${c1}" stroke="none"/><path d="m155-5-20-20-160 160 20 20z" fill="${c2}"/><path d="m45 205 160-160 20 20-160 160z" fill="${c2}"/><path d="m45-5 20-20 160 160-20 20-160-160" fill="${c2}"/><path d="m-5 45-20 20 160 160 20-20-160-160" fill="${c2}"/></pattern>`,
|
||||
maily: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.167}" height="${
|
||||
size * 0.167
|
||||
}" viewBox="0 0 200 200" stroke="#000" stroke-width="1.2"><path fill="${c1}" stroke="none" d="M0 0h200v200H0z"/><g fill="${c2}"><path d="m80-2c-5.27e-4 2.403-0.1094 6.806-0.3262 9.199 5.014-1.109 10.1-1.768 15.19-2.059 0.09325-1.712 0.1401-5.426 0.1406-7.141z"/><path d="m100 5a95 95 0 0 0-95 95 95 95 0 0 0 95 95 95 95 0 0 0 95-95 95 95 0 0 0-95-95zm0 15a80 80 0 0 1 80 80 80 80 0 0 1-80 80 80 80 0 0 1-80-80 80 80 0 0 1 80-80z"/><path d="m92.8 20.33c-5.562 0.4859-11.04 1.603-16.34 3.217-7.793 25.31-27.61 45.12-52.91 52.91-5.321 1.638-10.8 2.716-16.34 3.217-2.394 0.2168-6.796 0.3256-9.199 0.3262v15c1.714-4.79e-4 5.429-0.04737 7.141-0.1406 5.109-0.2761 10.19-0.9646 15.19-2.059 36.24-7.937 64.54-36.24 72.47-72.47z"/><path d="m202 80c-2.403-5.31e-4 -6.806-0.1094-9.199-0.3262 1.109 5.014 1.768 10.1 2.059 15.19 1.712 0.09326 5.426 0.1401 7.141 0.1406z"/><path d="m179.7 92.8c-0.4859-5.562-1.603-11.04-3.217-16.34-25.31-7.793-45.12-27.61-52.91-52.91-1.638-5.321-2.716-10.8-3.217-16.34-0.2168-2.394-0.3256-6.796-0.3262-9.199h-15c4.8e-4 1.714 0.0474 5.429 0.1406 7.141 0.2761 5.109 0.9646 10.19 2.059 15.19 7.937 36.24 36.24 64.54 72.47 72.47z"/><path d="m120 202c5.3e-4 -2.403 0.1094-6.806 0.3262-9.199-5.014 1.109-10.1 1.768-15.19 2.059-0.0933 1.712-0.1402 5.426-0.1406 7.141z"/><path d="m107.2 179.7c5.562-0.4859 11.04-1.603 16.34-3.217 7.793-25.31 27.61-45.12 52.91-52.91 5.321-1.638 10.8-2.716 16.34-3.217 2.394-0.2168 6.796-0.3256 9.199-0.3262v-15c-1.714 4.7e-4 -5.429 0.0474-7.141 0.1406-5.109 0.2761-10.19 0.9646-15.19 2.059-36.24 7.937-64.54 36.24-72.47 72.47z"/><path d="m -2,120 c 2.403,5.4e-4 6.806,0.1094 9.199,0.3262 -1.109,-5.014 -1.768,-10.1 -2.059,-15.19 -1.712,-0.0933 -5.426,-0.1402 -7.141,-0.1406 z"/><path d="m 20.33,107.2 c 0.4859,5.562 1.603,11.04 3.217,16.34 25.31,7.793 45.12,27.61 52.91,52.91 1.638,5.321 2.716,10.8 3.217,16.34 0.2168,2.394 0.3256,6.796 0.3262,9.199 L 95,202 c -4.8e-4,-1.714 -0.0472,-5.44 -0.1404,-7.152 -0.2761,-5.109 -0.9646,-10.19 -2.059,-15.19 -7.937,-36.24 -36.24,-64.54 -72.47,-72.47 z"/></g></pattern>`,
|
||||
honeycombed: (p: string, c1: string, c2: string, size: number) =>
|
||||
`<pattern id="${p}" width="${size * 0.143}" height="${
|
||||
size * 0.24514
|
||||
}" viewBox="0 0 70 120"><rect width="70" height="120" fill="${c1}"/><path d="M 70,0 V 20 L 35,40 m 35,80 V 100 L 35,80 M 0,120 V 100 L 35,80 V 40 L 0,20 V 0" stroke="${c2}" fill="none" stroke-width="3"/></pattern>`,
|
||||
};
|
||||
51
src/modules/emblem/positions.ts
Normal file
51
src/modules/emblem/positions.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
export const positions = {
|
||||
conventional: {
|
||||
e: 20,
|
||||
abcdefgzi: 3,
|
||||
beh: 3,
|
||||
behdf: 2,
|
||||
acegi: 1,
|
||||
kn: 3,
|
||||
bhdf: 1,
|
||||
jeo: 1,
|
||||
abc: 3,
|
||||
jln: 6,
|
||||
jlh: 3,
|
||||
kmo: 2,
|
||||
jleh: 1,
|
||||
def: 3,
|
||||
abcpqh: 4,
|
||||
ABCDEFGHIJKL: 1,
|
||||
},
|
||||
complex: {
|
||||
e: 40,
|
||||
beh: 1,
|
||||
kn: 1,
|
||||
jeo: 1,
|
||||
abc: 2,
|
||||
jln: 7,
|
||||
jlh: 2,
|
||||
def: 1,
|
||||
abcpqh: 1,
|
||||
},
|
||||
divisions: {
|
||||
perPale: { e: 15, pq: 5, jo: 2, jl: 2, ABCDEFGHIJKL: 1 },
|
||||
perFess: {
|
||||
e: 12,
|
||||
kn: 4,
|
||||
jkl: 2,
|
||||
gizgiz: 1,
|
||||
jlh: 3,
|
||||
kmo: 1,
|
||||
ABCDEFGHIJKL: 1,
|
||||
},
|
||||
perBend: { e: 5, lm: 5, bcfdgh: 1 },
|
||||
perBendSinister: { e: 1, jo: 1 },
|
||||
perCross: { e: 4, jlmo: 1, j: 1, jo: 2, jl: 1 },
|
||||
perChevron: { e: 1, jlh: 1, dfk: 1, dfbh: 2, bdefh: 1 },
|
||||
perChevronReversed: { e: 1, mok: 2, dfh: 2, dfbh: 1, bdefh: 1 },
|
||||
perSaltire: { bhdf: 8, e: 3, abcdefgzi: 1, bh: 1, df: 1, ABCDEFGHIJKL: 1 },
|
||||
perPile: { ee: 3, be: 2, abceh: 1, abcabc: 1, jleh: 1 },
|
||||
},
|
||||
inescutcheon: { e: 4, jln: 1 },
|
||||
};
|
||||
363
src/modules/emblem/renderer.ts
Normal file
363
src/modules/emblem/renderer.ts
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
import { shieldBox } from "./box";
|
||||
import { colors } from "./colors";
|
||||
import { lines } from "./lines";
|
||||
import { shieldPaths } from "./paths";
|
||||
import { patterns } from "./patterns";
|
||||
import { shieldPositions } from "./shieldPositions";
|
||||
import { shieldSize } from "./size";
|
||||
import { templates } from "./templates";
|
||||
|
||||
declare global {
|
||||
var COArenderer: EmblemRenderModule;
|
||||
}
|
||||
|
||||
interface Division {
|
||||
division: string;
|
||||
line?: string;
|
||||
t: string;
|
||||
}
|
||||
|
||||
interface Ordinary {
|
||||
ordinary: string;
|
||||
line?: string;
|
||||
t: string;
|
||||
divided?: "field" | "division" | "counter";
|
||||
above?: boolean;
|
||||
}
|
||||
|
||||
interface Charge {
|
||||
stroke: string;
|
||||
charge: string;
|
||||
t: string;
|
||||
size?: number;
|
||||
sinister?: boolean;
|
||||
reversed?: boolean;
|
||||
line?: string;
|
||||
divided?: "field" | "division" | "counter";
|
||||
p: number[]; // position on shield from 1 to 9
|
||||
}
|
||||
|
||||
interface Emblem {
|
||||
shield: string;
|
||||
t1: string;
|
||||
division?: Division;
|
||||
ordinaries?: Ordinary[];
|
||||
charges?: Charge[];
|
||||
custom?: boolean; // if true, coa will not be rendered
|
||||
}
|
||||
|
||||
class EmblemRenderModule {
|
||||
get shieldPaths() {
|
||||
return shieldPaths;
|
||||
}
|
||||
|
||||
private getTemplate(id: string, line?: string) {
|
||||
const linedId = `${id}Lined` as keyof typeof templates;
|
||||
if (!line || line === "straight" || !templates[linedId])
|
||||
return templates[id as keyof typeof templates]; // return regular template if no line or line is straight or lined template does not exist
|
||||
const linePath = lines[line as keyof typeof lines];
|
||||
return (templates[linedId] as (line: string) => string)(linePath);
|
||||
}
|
||||
|
||||
// get charge is string starts with "semy"
|
||||
private semy(input: string | undefined) {
|
||||
if (!input) return false;
|
||||
const isSemy = /^semy/.test(input);
|
||||
if (!isSemy) return false;
|
||||
const match = input.match(/semy_of_(.*?)-/);
|
||||
return match ? match[1] : false;
|
||||
}
|
||||
|
||||
private async fetchCharge(charge: string, id: string) {
|
||||
const fetched = fetch(`./charges/${charge}.svg`)
|
||||
.then((res) => {
|
||||
if (res.ok) return res.text();
|
||||
else throw new Error("Cannot fetch charge");
|
||||
})
|
||||
.then((text) => {
|
||||
const html = document.createElement("html");
|
||||
html.innerHTML = text;
|
||||
const g: SVGAElement = html.querySelector("g") as SVGAElement;
|
||||
g.setAttribute("id", `${charge}_${id}`);
|
||||
return g.outerHTML;
|
||||
})
|
||||
.catch((err) => {
|
||||
ERROR && console.error(err);
|
||||
});
|
||||
return fetched;
|
||||
}
|
||||
|
||||
private async getCharges(coa: Emblem, id: string, shieldPath: string) {
|
||||
const charges = coa.charges
|
||||
? coa.charges.map((charge) => charge.charge)
|
||||
: []; // add charges
|
||||
if (this.semy(coa.t1)) charges.push(this.semy(coa.t1) as string); // add field semy charge
|
||||
if (this.semy(coa.division?.t))
|
||||
charges.push(this.semy(coa.division?.t) as string); // add division semy charge
|
||||
|
||||
const uniqueCharges = [...new Set(charges)];
|
||||
const fetchedCharges = await Promise.all(
|
||||
uniqueCharges.map(async (charge) => {
|
||||
if (charge === "inescutcheon")
|
||||
return `<g id="inescutcheon_${id}"><path transform="translate(66 66) scale(.34)" d="${shieldPath}"/></g>`;
|
||||
const fetched = await this.fetchCharge(charge, id);
|
||||
return fetched;
|
||||
}),
|
||||
);
|
||||
return fetchedCharges.join("");
|
||||
}
|
||||
|
||||
// get color or link to pattern
|
||||
private clr(tincture: string) {
|
||||
return tincture in colors
|
||||
? colors[tincture as keyof typeof colors]
|
||||
: `url(#${tincture})`;
|
||||
}
|
||||
|
||||
private getSizeMod(size: string) {
|
||||
if (size === "small") return 0.8;
|
||||
if (size === "smaller") return 0.5;
|
||||
if (size === "smallest") return 0.25;
|
||||
if (size === "big") return 1.6;
|
||||
return 1;
|
||||
}
|
||||
|
||||
private getPatterns(coa: Emblem, id: string) {
|
||||
const isPattern = (string: string) => string.includes("-");
|
||||
const patternsToAdd = [];
|
||||
if (coa.t1.includes("-")) patternsToAdd.push(coa.t1); // add field pattern
|
||||
if (coa.division && isPattern(coa.division.t))
|
||||
patternsToAdd.push(coa.division.t); // add division pattern
|
||||
if (coa.ordinaries)
|
||||
coa.ordinaries
|
||||
.filter((ordinary) => isPattern(ordinary.t))
|
||||
.forEach((ordinary) => {
|
||||
patternsToAdd.push(ordinary.t); // add ordinaries pattern
|
||||
});
|
||||
if (coa.charges)
|
||||
coa.charges
|
||||
.filter((charge) => isPattern(charge.t))
|
||||
.forEach((charge) => {
|
||||
patternsToAdd.push(charge.t); // add charges pattern
|
||||
});
|
||||
if (!patternsToAdd.length) return "";
|
||||
|
||||
return [...new Set(patternsToAdd)]
|
||||
.map((patternString) => {
|
||||
const [pattern, t1, t2, size] = patternString.split("-");
|
||||
const charge = this.semy(patternString);
|
||||
if (charge)
|
||||
return patterns.semy(
|
||||
patternString,
|
||||
this.clr(t1),
|
||||
this.clr(t2),
|
||||
this.getSizeMod(size),
|
||||
`${charge}_${id}`,
|
||||
);
|
||||
return patterns[pattern as keyof typeof patterns](
|
||||
patternString,
|
||||
this.clr(t1),
|
||||
this.clr(t2),
|
||||
this.getSizeMod(size),
|
||||
charge as string,
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
private async draw(id: string, coa: Emblem) {
|
||||
const { shield = "heater", division, ordinaries = [], charges = [] } = coa;
|
||||
|
||||
const ordinariesRegular = ordinaries.filter((o) => !o.above);
|
||||
const ordinariesAboveCharges = ordinaries.filter((o) => o.above);
|
||||
const shieldPath =
|
||||
shield in shieldPaths
|
||||
? shieldPaths[shield as keyof typeof shieldPaths]
|
||||
: shieldPaths.heater;
|
||||
const tDiv = division
|
||||
? division.t.includes("-")
|
||||
? division.t.split("-")[1]
|
||||
: division.t
|
||||
: null;
|
||||
const positions =
|
||||
shield in shieldPositions
|
||||
? shieldPositions[shield as keyof typeof shieldPositions]
|
||||
: shieldPositions.heater;
|
||||
const sizeModifier =
|
||||
shield in shieldSize ? shieldSize[shield as keyof typeof shieldSize] : 1;
|
||||
const viewBox =
|
||||
shield in shieldBox
|
||||
? shieldBox[shield as keyof typeof shieldBox]
|
||||
: "0 0 200 200";
|
||||
|
||||
const shieldClip = `<clipPath id="${shield}_${id}"><path d="${shieldPath}"/></clipPath>`;
|
||||
const divisionClip = division
|
||||
? `<clipPath id="divisionClip_${id}">${this.getTemplate(division.division, division.line)}</clipPath>`
|
||||
: "";
|
||||
const loadedCharges = await this.getCharges(coa, id, shieldPath);
|
||||
const loadedPatterns = this.getPatterns(coa, id);
|
||||
const blacklight = `<radialGradient id="backlight_${id}" cx="100%" cy="100%" r="150%"><stop stop-color="#fff" stop-opacity=".3" offset="0"/><stop stop-color="#fff" stop-opacity=".15" offset=".25"/><stop stop-color="#000" stop-opacity="0" offset="1"/></radialGradient>`;
|
||||
const field = `<rect x="0" y="0" width="200" height="200" fill="${this.clr(coa.t1)}"/>`;
|
||||
const style = `<style>
|
||||
g.secondary,path.secondary {fill: var(--secondary);}
|
||||
g.tertiary,path.tertiary {fill: var(--tertiary);}
|
||||
</style>`;
|
||||
|
||||
const templateCharge = (
|
||||
charge: Charge,
|
||||
tincture: string,
|
||||
secondaryTincture?: string,
|
||||
tertiaryTincture?: string,
|
||||
) => {
|
||||
const primary = this.clr(tincture);
|
||||
const secondary = this.clr(secondaryTincture || tincture);
|
||||
const tertiary = this.clr(tertiaryTincture || tincture);
|
||||
const stroke = charge.stroke || "#000";
|
||||
|
||||
const chargePositions = [...new Set(charge.p)].filter(
|
||||
(position) => positions[position as unknown as keyof typeof positions],
|
||||
); // filter out invalid positions
|
||||
|
||||
let svg = `<g fill="${primary}" style="--secondary: ${secondary}; --tertiary: ${tertiary}" stroke="${stroke}">`;
|
||||
for (const p of chargePositions) {
|
||||
const transform = getElTransform(charge, p);
|
||||
svg += `<use href="#${charge.charge}_${id}" transform="${transform}"></use>`;
|
||||
}
|
||||
return `${svg}</g>`;
|
||||
|
||||
function getElTransform(c: Charge, p: string | number) {
|
||||
const s = (c.size || 1) * sizeModifier;
|
||||
const sx = c.sinister ? -s : s;
|
||||
const sy = c.reversed ? -s : s;
|
||||
let [x, y] = positions[p as keyof typeof positions];
|
||||
x = x - 100 * (sx - 1);
|
||||
y = y - 100 * (sy - 1);
|
||||
const scale = c.sinister || c.reversed ? `${sx} ${sy}` : s;
|
||||
return `translate(${x} ${y}) scale(${scale})`;
|
||||
}
|
||||
};
|
||||
|
||||
const templateOrdinary = (ordinary: Ordinary, tincture: string) => {
|
||||
const fill = this.clr(tincture);
|
||||
let svg = `<g fill="${fill}" stroke="none">`;
|
||||
if (ordinary.ordinary === "bordure")
|
||||
svg += `<path d="${shieldPath}" fill="none" stroke="${fill}" stroke-width="16.7%"/>`;
|
||||
else if (ordinary.ordinary === "orle")
|
||||
svg += `<path d="${shieldPath}" fill="none" stroke="${fill}" stroke-width="5%" transform="scale(.85)" transform-origin="center"/>`;
|
||||
else svg += this.getTemplate(ordinary.ordinary, ordinary.line);
|
||||
return `${svg}</g>`;
|
||||
};
|
||||
|
||||
const templateDivision = () => {
|
||||
let svg = "";
|
||||
|
||||
// In field part
|
||||
for (const ordinary of ordinariesRegular) {
|
||||
if (ordinary.divided === "field")
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === "counter")
|
||||
svg += templateOrdinary(ordinary, tDiv!);
|
||||
}
|
||||
|
||||
for (const charge of charges) {
|
||||
if (charge.divided === "field") svg += templateCharge(charge, charge.t);
|
||||
else if (charge.divided === "counter")
|
||||
svg += templateCharge(charge, tDiv!);
|
||||
}
|
||||
|
||||
for (const ordinary of ordinariesAboveCharges) {
|
||||
if (ordinary.divided === "field")
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === "counter")
|
||||
svg += templateOrdinary(ordinary, tDiv!);
|
||||
}
|
||||
|
||||
// In division part
|
||||
svg += `<g clip-path="url(#divisionClip_${id})"><rect x="0" y="0" width="200" height="200" fill="${this.clr(
|
||||
division!.t,
|
||||
)}"/>`;
|
||||
|
||||
for (const ordinary of ordinariesRegular) {
|
||||
if (ordinary.divided === "division")
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === "counter")
|
||||
svg += templateOrdinary(ordinary, coa.t1);
|
||||
}
|
||||
|
||||
for (const charge of charges) {
|
||||
if (charge.divided === "division")
|
||||
svg += templateCharge(charge, charge.t);
|
||||
else if (charge.divided === "counter")
|
||||
svg += templateCharge(charge, coa.t1);
|
||||
}
|
||||
|
||||
for (const ordinary of ordinariesAboveCharges) {
|
||||
if (ordinary.divided === "division")
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === "counter")
|
||||
svg += templateOrdinary(ordinary, coa.t1);
|
||||
}
|
||||
|
||||
svg += `</g>`;
|
||||
return svg;
|
||||
};
|
||||
|
||||
const templateAboveAll = () => {
|
||||
let svg = "";
|
||||
|
||||
ordinariesRegular
|
||||
.filter((o) => !o.divided)
|
||||
.forEach((ordinary) => {
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
});
|
||||
|
||||
charges
|
||||
.filter((o) => !o.divided || !division)
|
||||
.forEach((charge) => {
|
||||
svg += templateCharge(charge, charge.t);
|
||||
});
|
||||
|
||||
ordinariesAboveCharges
|
||||
.filter((o) => !o.divided)
|
||||
.forEach((ordinary) => {
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
});
|
||||
|
||||
return svg;
|
||||
};
|
||||
|
||||
const divisionGroup = division ? templateDivision() : "";
|
||||
const overlay = `<path d="${shieldPath}" fill="url(#backlight_${id})" stroke="#333"/>`;
|
||||
|
||||
const svg = `<svg id="${id}" width="200" height="200" viewBox="${viewBox}">
|
||||
<defs>${shieldClip}${divisionClip}${loadedCharges}${loadedPatterns}${blacklight}${style}</defs>
|
||||
<g clip-path="url(#${shield}_${id})">${field}${divisionGroup}${templateAboveAll()}</g>
|
||||
${overlay}</svg>`;
|
||||
|
||||
// insert coa svg to defs
|
||||
document.getElementById("coas")!.insertAdjacentHTML("beforeend", svg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// render coa if does not exist
|
||||
async trigger(id: string, coa: Emblem) {
|
||||
if (!coa) return console.warn(`Emblem ${id} is undefined`);
|
||||
if (coa.custom) return console.warn("Cannot render custom emblem", coa);
|
||||
if (!document.getElementById(id)) return this.draw(id, coa);
|
||||
}
|
||||
|
||||
async add(type: string, i: number, coa: Emblem, x: number, y: number) {
|
||||
const id = `${type}COA${i}`;
|
||||
const g: HTMLElement = document.getElementById(
|
||||
`${type}Emblems`,
|
||||
) as HTMLElement;
|
||||
|
||||
if (emblems.selectAll("use").size()) {
|
||||
const size = parseFloat(g.getAttribute("font-size") || "50");
|
||||
const use = `<use data-i="${i}" x="${x - size / 2}" y="${y - size / 2}" width="1em" height="1em" href="#${id}"/>`;
|
||||
g.insertAdjacentHTML("beforeend", use);
|
||||
}
|
||||
if (layerIsOn("toggleEmblems")) this.trigger(id, coa);
|
||||
}
|
||||
}
|
||||
window.COArenderer = new EmblemRenderModule();
|
||||
1347
src/modules/emblem/shieldPositions.ts
Normal file
1347
src/modules/emblem/shieldPositions.ts
Normal file
File diff suppressed because it is too large
Load diff
46
src/modules/emblem/shields.ts
Normal file
46
src/modules/emblem/shields.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
export const shields: {
|
||||
types: Record<string, number>;
|
||||
[key: string]: Record<string, number>;
|
||||
} = {
|
||||
types: {
|
||||
basic: 10,
|
||||
regional: 2,
|
||||
historical: 1,
|
||||
specific: 1,
|
||||
banner: 1,
|
||||
simple: 2,
|
||||
fantasy: 1,
|
||||
middleEarth: 0,
|
||||
},
|
||||
basic: { heater: 12, spanish: 6, french: 1 },
|
||||
regional: { horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1 },
|
||||
historical: {
|
||||
boeotian: 1,
|
||||
roman: 2,
|
||||
kite: 1,
|
||||
oldFrench: 5,
|
||||
renaissance: 2,
|
||||
baroque: 2,
|
||||
},
|
||||
specific: { targe: 1, targe2: 0, pavise: 5, wedged: 10 },
|
||||
banner: {
|
||||
flag: 1,
|
||||
pennon: 0,
|
||||
guidon: 0,
|
||||
banner: 0,
|
||||
dovetail: 1,
|
||||
gonfalon: 5,
|
||||
pennant: 0,
|
||||
},
|
||||
simple: { round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0 },
|
||||
fantasy: { fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3 },
|
||||
middleEarth: {
|
||||
noldor: 1,
|
||||
gondor: 1,
|
||||
easterling: 1,
|
||||
erebor: 1,
|
||||
ironHills: 1,
|
||||
urukHai: 1,
|
||||
moriaOrc: 1,
|
||||
},
|
||||
};
|
||||
32
src/modules/emblem/size.ts
Normal file
32
src/modules/emblem/size.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
export const shieldSize = {
|
||||
horsehead: 0.9,
|
||||
horsehead2: 0.9,
|
||||
polish: 0.85,
|
||||
swiss: 0.95,
|
||||
boeotian: 0.75,
|
||||
roman: 0.95,
|
||||
kite: 0.65,
|
||||
targe2: 0.9,
|
||||
pavise: 0.9,
|
||||
wedged: 0.95,
|
||||
flag: 0.7,
|
||||
pennon: 0.5,
|
||||
guidon: 0.65,
|
||||
banner: 0.8,
|
||||
dovetail: 0.8,
|
||||
pennant: 0.6,
|
||||
oval: 0.95,
|
||||
vesicaPiscis: 0.8,
|
||||
diamond: 0.8,
|
||||
no: 1.2,
|
||||
fantasy1: 0.8,
|
||||
fantasy2: 0.7,
|
||||
fantasy3: 0.7,
|
||||
fantasy5: 0.9,
|
||||
noldor: 0.5,
|
||||
gondor: 0.75,
|
||||
easterling: 0.8,
|
||||
erebor: 0.9,
|
||||
urukHai: 0.8,
|
||||
moriaOrc: 0.7,
|
||||
};
|
||||
98
src/modules/emblem/templates.ts
Normal file
98
src/modules/emblem/templates.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
export const templates: Record<string, string | ((line: string) => string)> = {
|
||||
// straight divisions
|
||||
perFess: `<rect x="0" y="100" width="200" height="100"/>`,
|
||||
perPale: `<rect x="100" y="0" width="100" height="200"/>`,
|
||||
perBend: `<polygon points="0,0 200,200 0,200"/>`,
|
||||
perBendSinister: `<polygon points="200,0 0,200 200,200"/>`,
|
||||
perChevron: `<polygon points="0,200 100,100 200,200"/>`,
|
||||
perChevronReversed: `<polygon points="0,0 100,100 200,0"/>`,
|
||||
perCross: `<rect x="100" y="0" width="100" height="100"/><rect x="0" y="100" width="100" height="100"/>`,
|
||||
perPile: `<polygon points="0,0 15,0 100,200 185,0 200,0 200,200 0,200"/>`,
|
||||
perSaltire: `<polygon points="0,0 0,200 200,0 200,200"/>`,
|
||||
gyronny: `<polygon points="0,0 200,200 200,100 0,100"/><polygon points="200,0 0,200 100,200 100,0"/>`,
|
||||
chevronny: `<path d="M0,80 100,-15 200,80 200,120 100,25 0,120z M0,160 100,65 200,160 200,200 100,105 0,200z M0,240 100,145 200,240 0,240z"/>`,
|
||||
// lined divisions
|
||||
perFessLined: (line: string) =>
|
||||
`<path d="${line}"/><rect x="0" y="115" width="200" height="85" shape-rendering="crispedges"/>`,
|
||||
perPaleLined: (line: string) =>
|
||||
`<path d="${line}" transform="rotate(-90 100 100)"/><rect x="115" y="0" width="85" height="200" shape-rendering="crispedges"/>`,
|
||||
perBendLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(-10 -10) rotate(45 110 110) scale(1.1)"/><rect x="0" y="115" width="200" height="85" transform="translate(-10 -10) rotate(45 110 110) scale(1.1)" shape-rendering="crispedges"/>`,
|
||||
perBendSinisterLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(-10 -10) rotate(-45 110 110) scale(1.1)"/><rect x="0" y="115" width="200" height="85" transform="translate(-10 -10) rotate(-45 110 110) scale(1.1)" shape-rendering="crispedges"/>`,
|
||||
perChevronLined: (line: string) =>
|
||||
`<rect x="15" y="115" width="200" height="200" transform="translate(70 70) rotate(45 100 100)"/><path d="${line}" transform="translate(129 71) rotate(-45 -100 100) scale(-1 1)"/><path d="${line}" transform="translate(71 71) rotate(45 100 100)"/>`,
|
||||
perChevronReversedLined: (line: string) =>
|
||||
`<rect x="15" y="115" width="200" height="200" transform="translate(-70 -70) rotate(225.001 100 100)"/><path d="${line}" transform="translate(-70.7 -70.7) rotate(225 100 100) scale(1 1)"/><path d="${line}" transform="translate(270.7 -70.7) rotate(-225 -100 100) scale(-1 1)"/>`,
|
||||
perCrossLined: (line: string) =>
|
||||
`<rect x="100" y="0" width="100" height="92.5"/><rect x="0" y="107.5" width="100" height="92.5"/><path d="${line}" transform="translate(0 50) scale(.5001)"/><path d="${line}" transform="translate(200 150) scale(-.5)"/>`,
|
||||
perPileLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(161.66 10) rotate(66.66 -100 100) scale(-1 1)"/><path d="${line}" transform="translate(38.33 10) rotate(-66.66 100 100)"/><polygon points="-2.15,0 84.15,200 115.85,200 202.15,0 200,200 0,200"/>`,
|
||||
// straight ordinaries
|
||||
fess: `<rect x="0" y="75" width="200" height="50"/>`,
|
||||
pale: `<rect x="75" y="0" width="50" height="200"/>`,
|
||||
bend: `<polygon points="35,0 200,165 200,200 165,200 0,35 0,0"/>`,
|
||||
bendSinister: `<polygon points="0,165 165,0 200,0 200,35 35,200 0,200"/>`,
|
||||
chief: `<rect width="200" height="75"/>`,
|
||||
bar: `<rect x="0" y="87.5" width="200" height="25"/>`,
|
||||
gemelle: `<rect x="0" y="76" width="200" height="16"/><rect x="0" y="108" width="200" height="16"/>`,
|
||||
fessCotissed: `<rect x="0" y="67" width="200" height="8"/><rect x="0" y="83" width="200" height="34"/><rect x="0" y="125" width="200" height="8"/>`,
|
||||
fessDoubleCotissed: `<rect x="0" y="60" width="200" height="7.5"/><rect x="0" y="72.5" width="200" height="7.5"/><rect x="0" y="85" width="200" height="30"/><rect x="0" y="120" width="200" height="7.5"/><rect x="0" y="132.5" width="200" height="7.5"/>`,
|
||||
bendlet: `<polygon points="22,0 200,178 200,200 178,200 0,22 0,0"/>`,
|
||||
bendletSinister: `<polygon points="0,178 178,0 200,0 200,22 22,200 0,200"/>`,
|
||||
terrace: `<rect x="0" y="145" width="200" height="55"/>`,
|
||||
cross: `<polygon points="85,0 85,85 0,85 0,115 85,115 85,200 115,200 115,115 200,115 200,85 115,85 115,0"/>`,
|
||||
crossParted: `<path d="M 80 0 L 80 80 L 0 80 L 0 95 L 80 95 L 80 105 L 0 105 L 0 120 L 80 120 L 80 200 L 95 200 L 95 120 L 105 120 L 105 200 L 120 200 L 120 120 L 200 120 L 200 105 L 120 105 L 120 95 L 200 95 L 200 80 L 120 80 L 120 0 L 105 0 L 105 80 L 95 80 L 95 0 L 80 0 z M 95 95 L 105 95 L 105 105 L 95 105 L 95 95 z"/>`,
|
||||
saltire: `<path d="M 0,21 79,100 0,179 0,200 21,200 100,121 179,200 200,200 200,179 121,100 200,21 200,0 179,0 100,79 21,0 0,0 Z"/>`,
|
||||
saltireParted: `<path d="M 7 0 L 89 82 L 82 89 L 0 7 L 0 28 L 72 100 L 0 172 L 0 193 L 82 111 L 89 118 L 7 200 L 28 200 L 100 128 L 172 200 L 193 200 L 111 118 L 118 111 L 200 193 L 200 172 L 128 100 L 200 28 L 200 7 L 118 89 L 111 82 L 193 0 L 172 0 L 100 72 L 28 0 L 7 0 z M 100 93 L 107 100 L 100 107 L 93 100 L 100 93 z"/>`,
|
||||
mount: `<path d="m0,250 a100,100,0,0,1,200,0"/>`,
|
||||
point: `<path d="M0,200 Q80,180 100,135 Q120,180 200,200"/>`,
|
||||
flaunches: `<path d="M0,0 q120,100 0,200 M200,0 q-120,100 0,200"/>`,
|
||||
gore: `<path d="M20,0 Q30,75 100,100 Q80,150 100,200 L0,200 L0,0 Z"/>`,
|
||||
pall: `<polygon points="0,0 30,0 100,70 170,0 200,0 200,30 122,109 122,200 78,200 78,109 0,30"/>`,
|
||||
pallReversed: `<polygon points="0,200 0,170 78,91 78,0 122,0 122,91 200,170 200,200 170,200 100,130 30,200"/>`,
|
||||
chevron: `<polygon points="0,125 100,60 200,125 200,165 100,100 0,165"/>`,
|
||||
chevronReversed: `<polygon points="0,75 100,140 200,75 200,35 100,100 0,35"/>`,
|
||||
gyron: `<polygon points="0,0 100,100 0,100"/>`,
|
||||
quarter: `<rect width="50%" height="50%"/>`,
|
||||
canton: `<rect width="37.5%" height="37.5%"/>`,
|
||||
pile: `<polygon points="70,0 100,175 130,0"/>`,
|
||||
pileInBend: `<polygon points="200,200 200,144 25,25 145,200"/>`,
|
||||
pileInBendSinister: `<polygon points="0,200 0,144 175,25 55,200"/>`,
|
||||
piles: `<polygon points="46,0 75,175 103,0"/><polygon points="95,0 125,175 154,0"/>`,
|
||||
pilesInPoint: `<path d="M15,0 100,200 60,0Z M80,0 100,200 120,0Z M140,0 100,200 185,0Z"/>`,
|
||||
label: `<path d="m 46,54.8 6.6,-15.6 95.1,0 5.9,15.5 -16.8,0.1 4.5,-11.8 L 104,43 l 4.3,11.9 -16.8,0 4.3,-11.8 -37.2,0 4.5,11.8 -16.9,0 z"/>`,
|
||||
// lined ordinaries
|
||||
fessLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0 -25)"/><path d="${line}" transform="translate(0 25) rotate(180 100 100)"/><rect x="0" y="88" width="200" height="24" stroke="none"/>`,
|
||||
paleLined: (line: string) =>
|
||||
`<path d="${line}" transform="rotate(-90 100 100) translate(0 -25)"/><path d="${line}" transform="rotate(90 100 100) translate(0 -25)"/><rect x="88" y="0" width="24" height="200" stroke="none"/>`,
|
||||
bendLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(8 -18) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-28 18) rotate(225 110 100) scale(1.1 1)"/><rect x="0" y="88" width="200" height="24" transform="translate(-10 0) rotate(45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
bendSinisterLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(-28 -18) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(8 18) rotate(-225 110 100) scale(1.1 1)"/><rect x="0" y="88" width="200" height="24" transform="translate(-10 0) rotate(-45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
chiefLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0,-25) rotate(180.00001 100 100)"/><rect width="200" height="62" stroke="none"/>`,
|
||||
barLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0,-12.5)"/><path d="${line}" transform="translate(0,12.5) rotate(180.00001 100 100)"/><rect x="0" y="94" width="200" height="12" stroke="none"/>`,
|
||||
gemelleLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0,-22.5)"/><path d="${line}" transform="translate(0,22.5) rotate(180.00001 100 100)"/>`,
|
||||
fessCotissedLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0 15) scale(1 .5)"/><path d="${line}" transform="translate(0 85) rotate(180 100 50) scale(1 .5)"/><rect x="0" y="80" width="200" height="40"/>`,
|
||||
fessDoubleCotissedLined: (line: string) =>
|
||||
`<rect x="0" y="85" width="200" height="30"/><rect x="0" y="72.5" width="200" height="7.5"/><rect x="0" y="120" width="200" height="7.5"/><path d="${line}" transform="translate(0 10) scale(1 .5)"/><path d="${line}" transform="translate(0 90) rotate(180 100 50) scale(1 .5)"/>`,
|
||||
bendletLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(2 -12) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-22 12) rotate(225 110 100) scale(1.1 1)"/><rect x="0" y="94" width="200" height="12" transform="translate(-10 0) rotate(45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
bendletSinisterLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(-22 -12) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(2 12) rotate(-225 110 100) scale(1.1 1)"/><rect x="0" y="94" width="200" height="12" transform="translate(-10 0) rotate(-45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
terraceLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0,50)"/><rect x="0" y="164" width="200" height="36" stroke="none"/>`,
|
||||
crossLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0,-14.5)"/><path d="${line}" transform="rotate(180 100 100) translate(0,-14.5)"/><path d="${line}" transform="rotate(-90 100 100) translate(0,-14.5)"/><path d="${line}" transform="rotate(-270 100 100) translate(0,-14.5)"/>`,
|
||||
crossPartedLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0,-20)"/><path d="${line}" transform="rotate(180 100 100) translate(0,-20)"/><path d="${line}" transform="rotate(-90 100 100) translate(0,-20)"/><path d="${line}" transform="rotate(-270 100 100) translate(0,-20)"/>`,
|
||||
saltireLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(0 -10) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-20 10) rotate(225 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-20 -10) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(0 10) rotate(-225 110 100) scale(1.1 1)"/>`,
|
||||
saltirePartedLined: (line: string) =>
|
||||
`<path d="${line}" transform="translate(3 -13) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-23 13) rotate(225 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-23 -13) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(3 13) rotate(-225 110 100) scale(1.1 1)"/>`,
|
||||
};
|
||||
43
src/modules/emblem/tinctures.ts
Normal file
43
src/modules/emblem/tinctures.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { P } from "../../utils";
|
||||
|
||||
export const createTinctures = () => ({
|
||||
field: { metals: 3, colours: 4, stains: +P(0.03), patterns: 1 },
|
||||
division: { metals: 5, colours: 8, stains: +P(0.03), patterns: 1 },
|
||||
charge: { metals: 2, colours: 3, stains: +P(0.05), patterns: 0 },
|
||||
metals: { argent: 3, or: 2 },
|
||||
colours: { gules: 5, azure: 4, sable: 3, purpure: 3, vert: 2 },
|
||||
stains: { murrey: 1, sanguine: 1, tenné: 1 },
|
||||
patterns: {
|
||||
semy: 8,
|
||||
ermine: 6,
|
||||
vair: 4,
|
||||
counterVair: 1,
|
||||
vairInPale: 1,
|
||||
vairEnPointe: 2,
|
||||
vairAncien: 2,
|
||||
potent: 2,
|
||||
counterPotent: 1,
|
||||
potentInPale: 1,
|
||||
potentEnPointe: 1,
|
||||
chequy: 8,
|
||||
lozengy: 5,
|
||||
fusily: 2,
|
||||
pally: 8,
|
||||
barry: 10,
|
||||
gemelles: 1,
|
||||
bendy: 8,
|
||||
bendySinister: 4,
|
||||
palyBendy: 2,
|
||||
barryBendy: 1,
|
||||
pappellony: 2,
|
||||
pappellony2: 3,
|
||||
scaly: 1,
|
||||
plumetty: 1,
|
||||
masoned: 6,
|
||||
fretty: 3,
|
||||
grillage: 1,
|
||||
chainy: 1,
|
||||
maily: 2,
|
||||
honeycombed: 1,
|
||||
},
|
||||
});
|
||||
187
src/modules/emblem/typeMapping.ts
Normal file
187
src/modules/emblem/typeMapping.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// Charges specific to culture or burg type (FMG-only config, not coming from Armoria)
|
||||
export const typeMapping: Record<string, Record<string, number>> = {
|
||||
Naval: {
|
||||
anchor: 3,
|
||||
drakkar: 1,
|
||||
lymphad: 2,
|
||||
caravel: 1,
|
||||
shipWheel: 1,
|
||||
armillarySphere: 1,
|
||||
escallop: 1,
|
||||
dolphin: 1,
|
||||
plaice: 1,
|
||||
},
|
||||
Highland: {
|
||||
tower: 1,
|
||||
raven: 1,
|
||||
wolfHeadErased: 1,
|
||||
wolfPassant: 1,
|
||||
goat: 1,
|
||||
axe: 1,
|
||||
},
|
||||
River: {
|
||||
garb: 1,
|
||||
rake: 1,
|
||||
raft: 1,
|
||||
boat: 2,
|
||||
drakkar: 2,
|
||||
hook: 2,
|
||||
pike: 2,
|
||||
bullHeadCaboshed: 1,
|
||||
apple: 1,
|
||||
pear: 1,
|
||||
plough: 1,
|
||||
earOfWheat: 1,
|
||||
salmon: 1,
|
||||
cancer: 1,
|
||||
bridge: 1,
|
||||
bridge2: 2,
|
||||
sickle: 1,
|
||||
scythe: 1,
|
||||
grapeBunch: 1,
|
||||
wheatStalk: 1,
|
||||
windmill: 1,
|
||||
crocodile: 1,
|
||||
},
|
||||
Lake: {
|
||||
hook: 3,
|
||||
cancer: 2,
|
||||
escallop: 1,
|
||||
pike: 2,
|
||||
heron: 1,
|
||||
boat: 1,
|
||||
boat2: 2,
|
||||
salmon: 1,
|
||||
sickle: 1,
|
||||
windmill: 1,
|
||||
swanErased: 1,
|
||||
swan: 1,
|
||||
frog: 1,
|
||||
wasp: 1,
|
||||
},
|
||||
Nomadic: {
|
||||
pot: 1,
|
||||
buckle: 1,
|
||||
wheel: 2,
|
||||
sabre: 2,
|
||||
sabresCrossed: 1,
|
||||
bow: 2,
|
||||
arrow: 1,
|
||||
horseRampant: 1,
|
||||
horseSalient: 1,
|
||||
crescent: 1,
|
||||
camel: 3,
|
||||
scorpion: 1,
|
||||
falcon: 1,
|
||||
},
|
||||
Hunting: {
|
||||
bugleHorn: 2,
|
||||
bugleHorn2: 1,
|
||||
stagsAttires: 2,
|
||||
attire: 2,
|
||||
hatchet: 1,
|
||||
bowWithArrow: 2,
|
||||
arrowsSheaf: 1,
|
||||
lanceHead: 1,
|
||||
saw: 1,
|
||||
deerHeadCaboshed: 1,
|
||||
wolfStatant: 1,
|
||||
oak: 1,
|
||||
pineCone: 1,
|
||||
pineTree: 1,
|
||||
owl: 1,
|
||||
falcon: 1,
|
||||
peacock: 1,
|
||||
boarHeadErased: 2,
|
||||
horseHeadCouped: 1,
|
||||
rabbitSejant: 1,
|
||||
wolfRampant: 1,
|
||||
wolfPassant: 1,
|
||||
greyhoundCourant: 1,
|
||||
greyhoundRampant: 1,
|
||||
greyhoundSejant: 1,
|
||||
mastiffStatant: 1,
|
||||
talbotPassant: 1,
|
||||
talbotSejant: 1,
|
||||
stagPassant: 21,
|
||||
},
|
||||
// Selection based on type
|
||||
City: {
|
||||
key: 4,
|
||||
bell: 3,
|
||||
lute: 1,
|
||||
tower: 1,
|
||||
pillar: 1,
|
||||
castle: 1,
|
||||
castle2: 1,
|
||||
portcullis: 1,
|
||||
mallet: 1,
|
||||
cannon: 1,
|
||||
anvil: 1,
|
||||
buckle: 1,
|
||||
horseshoe: 1,
|
||||
stirrup: 1,
|
||||
lanceWithBanner: 1,
|
||||
bookClosed: 1,
|
||||
scissors: 1,
|
||||
scissors2: 1,
|
||||
shears: 1,
|
||||
pincers: 1,
|
||||
bridge: 2,
|
||||
archer: 1,
|
||||
shield: 1,
|
||||
arbalest: 1,
|
||||
arbalest2: 1,
|
||||
bowWithThreeArrows: 1,
|
||||
spear: 1,
|
||||
lochaberAxe: 1,
|
||||
armEmbowedHoldingSabre: 1,
|
||||
grenade: 1,
|
||||
maces: 1,
|
||||
grapeBunch: 1,
|
||||
cock: 1,
|
||||
ramHeadErased: 1,
|
||||
ratRampant: 1,
|
||||
hourglass: 1,
|
||||
scale: 1,
|
||||
scrollClosed: 1,
|
||||
},
|
||||
Capital: {
|
||||
crown: 2,
|
||||
crown2: 2,
|
||||
laurelWreath: 1,
|
||||
orb: 1,
|
||||
lute: 1,
|
||||
lyre: 1,
|
||||
banner: 1,
|
||||
castle: 1,
|
||||
castle2: 1,
|
||||
palace: 1,
|
||||
column: 1,
|
||||
lionRampant: 1,
|
||||
stagLodgedRegardant: 1,
|
||||
drawingCompass: 1,
|
||||
rapier: 1,
|
||||
scaleImbalanced: 1,
|
||||
scalesHanging: 1,
|
||||
},
|
||||
Сathedra: {
|
||||
crossHummetty: 3,
|
||||
mitre: 3,
|
||||
chalice: 1,
|
||||
orb: 1,
|
||||
crosier: 2,
|
||||
lamb: 1,
|
||||
monk: 2,
|
||||
angel: 3,
|
||||
crossLatin: 2,
|
||||
crossPatriarchal: 1,
|
||||
crossOrthodox: 1,
|
||||
crossCalvary: 1,
|
||||
agnusDei: 3,
|
||||
bookOpen: 1,
|
||||
sceptre: 1,
|
||||
bone: 1,
|
||||
skull: 1,
|
||||
},
|
||||
};
|
||||
|
|
@ -1,272 +1,351 @@
|
|||
"use strict";
|
||||
import { byId } from "../utils";
|
||||
|
||||
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"},
|
||||
declare global {
|
||||
var declareFont: (font: FontDefinition) => void;
|
||||
var getUsedFonts: (svg: SVGSVGElement) => FontDefinition[];
|
||||
var loadFontsAsDataURI: (
|
||||
fonts: FontDefinition[],
|
||||
) => Promise<FontDefinition[]>;
|
||||
var addGoogleFont: (family: string) => Promise<void>;
|
||||
var addLocalFont: (family: string) => void;
|
||||
var addWebFont: (family: string, src: string) => void;
|
||||
var fonts: FontDefinition[];
|
||||
}
|
||||
|
||||
type FontDefinition = {
|
||||
family: string;
|
||||
src?: string;
|
||||
unicodeRange?: string;
|
||||
variant?: string;
|
||||
};
|
||||
|
||||
window.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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
}
|
||||
"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;
|
||||
window.declareFont = (font: FontDefinition) => {
|
||||
const { family, src, ...rest } = font;
|
||||
addFontOption(family);
|
||||
|
||||
if (!src) return;
|
||||
const fontFace = new FontFace(family, src, {...rest, display: "block"});
|
||||
const fontFace = new FontFace(family, src, { ...rest, display: "block" });
|
||||
document.fonts.add(fontFace);
|
||||
}
|
||||
};
|
||||
|
||||
declareDefaultFonts(); // execute once on load
|
||||
|
||||
function declareDefaultFonts() {
|
||||
fonts.forEach(font => declareFont(font));
|
||||
fonts.forEach((font) => {
|
||||
declareFont(font);
|
||||
});
|
||||
}
|
||||
|
||||
function getUsedFonts(svg) {
|
||||
function addFontOption(family: string) {
|
||||
const options = document.getElementById("styleSelectFont")!;
|
||||
const option = document.createElement("option");
|
||||
option.value = family;
|
||||
option.innerText = family;
|
||||
option.style.fontFamily = family;
|
||||
options.append(option);
|
||||
}
|
||||
|
||||
async function fetchGoogleFont(family: string) {
|
||||
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: FontDefinition = { 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: Blob) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result as string);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
window.loadFontsAsDataURI = async (fonts: FontDefinition[]) => {
|
||||
const promises = fonts.map(async (font) => {
|
||||
const url = font.src?.match(/url\(['"]?(.+?)['"]?\)/)?.[1];
|
||||
if (!url) return font;
|
||||
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);
|
||||
};
|
||||
|
||||
window.getUsedFonts = (svg: SVGSVGElement) => {
|
||||
const usedFontFamilies = new Set();
|
||||
|
||||
const labelGroups = svg.querySelectorAll("#labels g");
|
||||
|
|
@ -282,112 +361,66 @@ function getUsedFonts(svg) {
|
|||
const legendFont = legend?.getAttribute("font-family");
|
||||
if (legendFont) usedFontFamilies.add(legendFont);
|
||||
|
||||
const usedFonts = fonts.filter(font => usedFontFamilies.has(font.family));
|
||||
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) {
|
||||
window.addGoogleFont = async (family: string) => {
|
||||
const fontRanges = await fetchGoogleFont(family);
|
||||
if (!fontRanges) return tip("Cannot fetch Google font for this value", true, "error", 4000);
|
||||
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"});
|
||||
const promises = fontRanges.map((range) => {
|
||||
const { src, unicodeRange } = range;
|
||||
const fontFace = new FontFace(family, src!, {
|
||||
unicodeRange,
|
||||
display: "block",
|
||||
});
|
||||
return fontFace.load();
|
||||
});
|
||||
|
||||
Promise.all(promises)
|
||||
.then(fontFaces => {
|
||||
fontFaces.forEach(fontFace => document.fonts.add(fontFace));
|
||||
.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;
|
||||
const select = byId<HTMLSelectElement>("styleSelectFont");
|
||||
if (select) select.value = family;
|
||||
changeFont();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
tip(`Failed to load Google font ${family}`, true, "error", 4000);
|
||||
ERROR && console.error(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function addLocalFont(family) {
|
||||
fonts.push({family});
|
||||
window.addLocalFont = (family: string) => {
|
||||
fonts.push({ family });
|
||||
|
||||
const fontFace = new FontFace(family, `local(${family})`, {display: "block"});
|
||||
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;
|
||||
const select = byId<HTMLSelectElement>("styleSelectFont");
|
||||
if (select) select.value = family;
|
||||
changeFont();
|
||||
}
|
||||
};
|
||||
|
||||
function addWebFont(family, url) {
|
||||
window.addWebFont = (family: string, url: string) => {
|
||||
const src = `url('${url}')`;
|
||||
fonts.push({family, src});
|
||||
fonts.push({ family, src });
|
||||
|
||||
const fontFace = new FontFace(family, src, {display: "block"});
|
||||
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;
|
||||
const select = byId<HTMLSelectElement>("styleSelectFont");
|
||||
if (select) select.value = family;
|
||||
changeFont();
|
||||
}
|
||||
};
|
||||
|
|
@ -13,4 +13,7 @@ import "./states-generator";
|
|||
import "./zones-generator";
|
||||
import "./religions-generator";
|
||||
import "./provinces-generator";
|
||||
import "./emblem";
|
||||
import "./ice";
|
||||
import "./markers-generator";
|
||||
import "./fonts";
|
||||
|
|
|
|||
1802
src/modules/markers-generator.ts
Normal file
1802
src/modules/markers-generator.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -330,7 +330,7 @@ class ProvinceModule {
|
|||
: P(0.3);
|
||||
const kinship = dominion ? 0 : 0.4;
|
||||
const type = Burgs.getType(center, burgs[burg]?.port);
|
||||
const coa = COA.generate(s.coa, kinship, dominion, type);
|
||||
const coa = COA.generate(s.coa, kinship, dominion ? 1 : 0, type);
|
||||
coa.shield = COA.getShield(c, s.i);
|
||||
|
||||
provinces.push({
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class StatesModule {
|
|||
const name = Names.getState(basename, burg.culture!);
|
||||
const type = pack.cultures[burg.culture!].type;
|
||||
const coa = COA.generate(null, null, null, type);
|
||||
coa.shield = COA.getShield(burg.culture, null);
|
||||
coa.shield = COA.getShield(burg.culture!);
|
||||
states.push({
|
||||
i: burg.i,
|
||||
name,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ declare global {
|
|||
var mapName: HTMLInputElement;
|
||||
var religionsNumber: HTMLInputElement;
|
||||
var distanceUnitInput: HTMLInputElement;
|
||||
var heightUnit: HTMLSelectElement;
|
||||
|
||||
var rivers: Selection<SVGElement, unknown, null, undefined>;
|
||||
var oceanLayers: Selection<SVGGElement, unknown, null, undefined>;
|
||||
|
|
@ -46,6 +47,7 @@ declare global {
|
|||
var defs: Selection<SVGDefsElement, unknown, null, undefined>;
|
||||
var coastline: Selection<SVGGElement, unknown, null, undefined>;
|
||||
var lakes: Selection<SVGGElement, unknown, null, undefined>;
|
||||
var provs: Selection<SVGGElement, unknown, null, undefined>;
|
||||
var getColorScheme: (scheme: string | null) => (t: number) => string;
|
||||
var getColor: (height: number, scheme: (t: number) => string) => string;
|
||||
var svgWidth: number;
|
||||
|
|
@ -62,7 +64,6 @@ declare global {
|
|||
icons: string[][];
|
||||
cost: number[];
|
||||
};
|
||||
var COA: any;
|
||||
var notes: any[];
|
||||
var style: {
|
||||
burgLabels: { [key: string]: { [key: string]: string } };
|
||||
|
|
@ -74,16 +75,18 @@ declare global {
|
|||
var layerIsOn: (layerId: string) => boolean;
|
||||
var drawRoute: (route: any) => void;
|
||||
var invokeActiveZooming: () => void;
|
||||
var COArenderer: { trigger: (id: string, coa: any) => void };
|
||||
var FlatQueue: any;
|
||||
|
||||
var tip: (
|
||||
message: string,
|
||||
autoHide?: boolean,
|
||||
type?: "info" | "warning" | "error",
|
||||
type?: "info" | "warn" | "error" | "success",
|
||||
timeout?: number,
|
||||
) => void;
|
||||
var locked: (settingId: string) => boolean;
|
||||
var unlock: (settingId: string) => void;
|
||||
var $: (selector: any) => any;
|
||||
var scale: number;
|
||||
var changeFont: () => void;
|
||||
var getFriendlyHeight: (coords: [number, number]) => string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export const byId = document.getElementById.bind(document);
|
||||
export const byId = <T extends HTMLElement>(id: string): T | undefined =>
|
||||
document.getElementById(id) as T;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
221
tests/e2e/load-map.spec.ts
Normal file
221
tests/e2e/load-map.spec.ts
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import path from "path";
|
||||
|
||||
test.describe("Map loading", () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await context.clearCookies();
|
||||
|
||||
await page.goto("/");
|
||||
await page.evaluate(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
// Wait for the hidden file input to be available
|
||||
await page.waitForSelector("#mapToLoad", { state: "attached" });
|
||||
});
|
||||
|
||||
test("should load a saved map file", async ({ page }) => {
|
||||
// Track errors during map loading
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (error) => errors.push(`pageerror: ${error.message}`));
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
errors.push(`console.error: ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Get the file input element and upload the map file
|
||||
const fileInput = page.locator("#mapToLoad");
|
||||
const mapFilePath = path.join(__dirname, "../fixtures/demo.map");
|
||||
await fileInput.setInputFiles(mapFilePath);
|
||||
|
||||
// Wait for map to be fully loaded
|
||||
// mapId is set at the very end of map loading in showStatistics()
|
||||
await page.waitForFunction(() => (window as any).mapId !== undefined, {
|
||||
timeout: 120000,
|
||||
});
|
||||
|
||||
// Additional wait for rendering to settle
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify map data is loaded
|
||||
const mapData = await page.evaluate(() => {
|
||||
const pack = (window as any).pack;
|
||||
return {
|
||||
hasStates: pack.states && pack.states.length > 1,
|
||||
hasBurgs: pack.burgs && pack.burgs.length > 1,
|
||||
hasCells: pack.cells && pack.cells.i && pack.cells.i.length > 0,
|
||||
hasRivers: pack.rivers && pack.rivers.length > 0,
|
||||
mapId: (window as any).mapId,
|
||||
};
|
||||
});
|
||||
|
||||
expect(mapData.hasStates).toBe(true);
|
||||
expect(mapData.hasBurgs).toBe(true);
|
||||
expect(mapData.hasCells).toBe(true);
|
||||
expect(mapData.hasRivers).toBe(true);
|
||||
expect(mapData.mapId).toBeDefined();
|
||||
|
||||
// Ensure no JavaScript errors occurred during loading
|
||||
// Filter out expected errors (external resources like Google Analytics, fonts)
|
||||
const criticalErrors = errors.filter(
|
||||
(e) =>
|
||||
!e.includes("fonts.googleapis.com") &&
|
||||
!e.includes("google-analytics") &&
|
||||
!e.includes("googletagmanager") &&
|
||||
!e.includes("Failed to load resource")
|
||||
);
|
||||
expect(criticalErrors).toEqual([]);
|
||||
});
|
||||
|
||||
test("loaded map should have correct SVG structure", async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (error) => errors.push(`pageerror: ${error.message}`));
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
errors.push(`console.error: ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
const fileInput = page.locator("#mapToLoad");
|
||||
const mapFilePath = path.join(__dirname, "../fixtures/demo.map");
|
||||
await fileInput.setInputFiles(mapFilePath);
|
||||
|
||||
await page.waitForFunction(() => (window as any).mapId !== undefined, {
|
||||
timeout: 120000,
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check essential SVG layers exist
|
||||
const layers = await page.evaluate(() => {
|
||||
return {
|
||||
ocean: !!document.getElementById("ocean"),
|
||||
lakes: !!document.getElementById("lakes"),
|
||||
coastline: !!document.getElementById("coastline"),
|
||||
rivers: !!document.getElementById("rivers"),
|
||||
borders: !!document.getElementById("borders"),
|
||||
burgs: !!document.getElementById("burgIcons"),
|
||||
labels: !!document.getElementById("labels"),
|
||||
};
|
||||
});
|
||||
|
||||
expect(layers.ocean).toBe(true);
|
||||
expect(layers.lakes).toBe(true);
|
||||
expect(layers.coastline).toBe(true);
|
||||
expect(layers.rivers).toBe(true);
|
||||
expect(layers.borders).toBe(true);
|
||||
expect(layers.burgs).toBe(true);
|
||||
expect(layers.labels).toBe(true);
|
||||
|
||||
const criticalErrors = errors.filter(
|
||||
(e) =>
|
||||
!e.includes("fonts.googleapis.com") &&
|
||||
!e.includes("google-analytics") &&
|
||||
!e.includes("googletagmanager") &&
|
||||
!e.includes("Failed to load resource")
|
||||
);
|
||||
expect(criticalErrors).toEqual([]);
|
||||
});
|
||||
|
||||
test("loaded map should preserve state data", async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (error) => errors.push(`pageerror: ${error.message}`));
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
errors.push(`console.error: ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
const fileInput = page.locator("#mapToLoad");
|
||||
const mapFilePath = path.join(__dirname, "../fixtures/demo.map");
|
||||
await fileInput.setInputFiles(mapFilePath);
|
||||
|
||||
await page.waitForFunction(() => (window as any).mapId !== undefined, {
|
||||
timeout: 120000,
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify states have proper structure
|
||||
const statesData = await page.evaluate(() => {
|
||||
const pack = (window as any).pack;
|
||||
const states = pack.states.filter((s: any) => s.i !== 0); // exclude neutral
|
||||
|
||||
return {
|
||||
count: states.length,
|
||||
allHaveNames: states.every((s: any) => s.name && s.name.length > 0),
|
||||
allHaveCells: states.every((s: any) => s.cells > 0),
|
||||
allHaveArea: states.every((s: any) => s.area > 0),
|
||||
};
|
||||
});
|
||||
|
||||
expect(statesData.count).toBeGreaterThan(0);
|
||||
expect(statesData.allHaveNames).toBe(true);
|
||||
expect(statesData.allHaveCells).toBe(true);
|
||||
expect(statesData.allHaveArea).toBe(true);
|
||||
|
||||
const criticalErrors = errors.filter(
|
||||
(e) =>
|
||||
!e.includes("fonts.googleapis.com") &&
|
||||
!e.includes("google-analytics") &&
|
||||
!e.includes("googletagmanager") &&
|
||||
!e.includes("Failed to load resource")
|
||||
);
|
||||
expect(criticalErrors).toEqual([]);
|
||||
});
|
||||
|
||||
test("loaded map should preserve burg data", async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (error) => errors.push(`pageerror: ${error.message}`));
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
errors.push(`console.error: ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
const fileInput = page.locator("#mapToLoad");
|
||||
const mapFilePath = path.join(__dirname, "../fixtures/demo.map");
|
||||
await fileInput.setInputFiles(mapFilePath);
|
||||
|
||||
await page.waitForFunction(() => (window as any).mapId !== undefined, {
|
||||
timeout: 120000,
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify burgs have proper structure
|
||||
const burgsData = await page.evaluate(() => {
|
||||
const pack = (window as any).pack;
|
||||
// Filter out placeholder (i=0) and removed burgs (removed=true or no name)
|
||||
const activeBurgs = pack.burgs.filter(
|
||||
(b: any) => b.i !== 0 && !b.removed && b.name
|
||||
);
|
||||
|
||||
return {
|
||||
count: activeBurgs.length,
|
||||
allHaveNames: activeBurgs.every(
|
||||
(b: any) => b.name && b.name.length > 0
|
||||
),
|
||||
allHaveCoords: activeBurgs.every(
|
||||
(b: any) => typeof b.x === "number" && typeof b.y === "number"
|
||||
),
|
||||
allHaveCells: activeBurgs.every(
|
||||
(b: any) => typeof b.cell === "number"
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
expect(burgsData.count).toBeGreaterThan(0);
|
||||
expect(burgsData.allHaveNames).toBe(true);
|
||||
expect(burgsData.allHaveCoords).toBe(true);
|
||||
expect(burgsData.allHaveCells).toBe(true);
|
||||
|
||||
const criticalErrors = errors.filter(
|
||||
(e) =>
|
||||
!e.includes("fonts.googleapis.com") &&
|
||||
!e.includes("google-analytics") &&
|
||||
!e.includes("googletagmanager") &&
|
||||
!e.includes("Failed to load resource")
|
||||
);
|
||||
expect(criticalErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
349
tests/e2e/zones-export.spec.ts
Normal file
349
tests/e2e/zones-export.spec.ts
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Zone Export", () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await context.clearCookies();
|
||||
|
||||
await page.goto("/");
|
||||
await page.evaluate(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
// Navigate with seed parameter and wait for full load
|
||||
await page.goto("/?seed=test-zones-export&width=1280&height=720");
|
||||
|
||||
// Wait for map generation to complete
|
||||
await page.waitForFunction(
|
||||
() => (window as any).mapId !== undefined,
|
||||
{ timeout: 60000 }
|
||||
);
|
||||
|
||||
// Additional wait for any rendering/animations to settle
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
// Helper function to create a test zone programmatically
|
||||
// Uses BFS to select a contiguous set of land cells for stable, representative testing
|
||||
async function createTestZone(page: any): Promise<number> {
|
||||
return await page.evaluate(() => {
|
||||
const { cells, zones } = (window as any).pack;
|
||||
|
||||
// Find a starting land cell (height >= 20)
|
||||
const totalCells = cells.i.length;
|
||||
let startCell = -1;
|
||||
for (let i = 1; i < totalCells; i++) {
|
||||
if (cells.h[i] >= 20) {
|
||||
startCell = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startCell === -1) {
|
||||
throw new Error("No land cells found to create a test zone");
|
||||
}
|
||||
|
||||
// Use BFS to select a contiguous set of 10-20 land cells
|
||||
const zoneCells: number[] = [];
|
||||
const visited = new Set<number>();
|
||||
const queue: number[] = [];
|
||||
|
||||
visited.add(startCell);
|
||||
queue.push(startCell);
|
||||
|
||||
while (queue.length > 0 && zoneCells.length < 20) {
|
||||
const current = queue.shift() as number;
|
||||
|
||||
// Only include land cells in the zone
|
||||
if (cells.h[current] >= 20) {
|
||||
zoneCells.push(current);
|
||||
}
|
||||
|
||||
// Explore neighbors
|
||||
const neighbors: number[] = cells.c[current] || [];
|
||||
for (const neighbor of neighbors) {
|
||||
if (neighbor && !visited.has(neighbor)) {
|
||||
visited.add(neighbor);
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneCells.length < 10) {
|
||||
throw new Error(`Not enough contiguous land cells found: ${zoneCells.length}`);
|
||||
}
|
||||
|
||||
// Generate unique zone ID
|
||||
const zoneId = zones.length;
|
||||
|
||||
// Create zone object
|
||||
const zone = {
|
||||
i: zoneId,
|
||||
name: "Test Export Zone",
|
||||
type: "Test",
|
||||
color: "#FF0000",
|
||||
cells: zoneCells,
|
||||
};
|
||||
|
||||
// Add zone to pack.zones array
|
||||
zones.push(zone);
|
||||
|
||||
return zoneId;
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to export zones to GeoJSON without file download
|
||||
// This calls the production code from public/modules/io/export.js
|
||||
async function exportZonesToGeoJson(page: any): Promise<any> {
|
||||
return await page.evaluate(() => {
|
||||
// Mock downloadFile to capture the JSON instead of downloading
|
||||
const originalDownloadFile = (window as any).downloadFile;
|
||||
let capturedJson: any = null;
|
||||
|
||||
(window as any).downloadFile = (data: string) => {
|
||||
capturedJson = JSON.parse(data);
|
||||
};
|
||||
|
||||
// Call the production code
|
||||
(window as any).saveGeoJsonZones();
|
||||
|
||||
// Restore original downloadFile
|
||||
(window as any).downloadFile = originalDownloadFile;
|
||||
|
||||
return capturedJson;
|
||||
});
|
||||
}
|
||||
|
||||
test("should export zone with valid GeoJSON root structure", async ({ page }) => {
|
||||
// Create a test zone
|
||||
const zoneId = await createTestZone(page);
|
||||
expect(zoneId).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Export zones to GeoJSON
|
||||
const geoJson = await exportZonesToGeoJson(page);
|
||||
|
||||
// Validate root GeoJSON structure (Task 5.1)
|
||||
expect(geoJson).toBeDefined();
|
||||
expect(geoJson).toHaveProperty("type");
|
||||
expect(geoJson.type).toBe("FeatureCollection");
|
||||
|
||||
expect(geoJson).toHaveProperty("features");
|
||||
expect(Array.isArray(geoJson.features)).toBe(true);
|
||||
expect(geoJson.features.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify the test zone is in the export
|
||||
const testZoneFeature = geoJson.features.find((f: any) => f.properties.id === zoneId);
|
||||
expect(testZoneFeature).toBeDefined();
|
||||
expect(testZoneFeature.properties.name).toBe("Test Export Zone");
|
||||
|
||||
// Validate Feature structure (Task 5.2)
|
||||
expect(testZoneFeature).toHaveProperty("type");
|
||||
expect(testZoneFeature.type).toBe("Feature");
|
||||
|
||||
expect(testZoneFeature).toHaveProperty("geometry");
|
||||
expect(testZoneFeature.geometry).toBeDefined();
|
||||
expect(typeof testZoneFeature.geometry).toBe("object");
|
||||
|
||||
expect(testZoneFeature.geometry).toHaveProperty("type");
|
||||
// Note: Geometry type can be "Polygon" (single component) or "MultiPolygon" (multiple disconnected components)
|
||||
// For this test with contiguous BFS-selected cells, we expect "Polygon"
|
||||
expect(testZoneFeature.geometry.type).toBe("Polygon");
|
||||
|
||||
expect(testZoneFeature.geometry).toHaveProperty("coordinates");
|
||||
expect(Array.isArray(testZoneFeature.geometry.coordinates)).toBe(true);
|
||||
|
||||
expect(testZoneFeature).toHaveProperty("properties");
|
||||
expect(testZoneFeature.properties).toBeDefined();
|
||||
expect(typeof testZoneFeature.properties).toBe("object");
|
||||
|
||||
// Task 6.1: Validate zone property mapping
|
||||
// Get the test zone from pack.zones in browser context
|
||||
const testZone = await page.evaluate((id: number) => {
|
||||
const { zones } = (window as any).pack;
|
||||
return zones.find((z: any) => z.i === id);
|
||||
}, zoneId);
|
||||
|
||||
expect(testZone).toBeDefined();
|
||||
|
||||
// Assert feature.properties match zone properties
|
||||
expect(testZoneFeature.properties.id).toBe(testZone.i);
|
||||
expect(testZoneFeature.properties.name).toBe(testZone.name);
|
||||
expect(testZoneFeature.properties.type).toBe(testZone.type);
|
||||
expect(testZoneFeature.properties.color).toBe(testZone.color);
|
||||
expect(testZoneFeature.properties.cells).toEqual(testZone.cells);
|
||||
|
||||
// Task 7.1: Validate coordinate array structure
|
||||
const { coordinates } = testZoneFeature.geometry;
|
||||
|
||||
// Assert geometry.coordinates is an array
|
||||
expect(Array.isArray(coordinates)).toBe(true);
|
||||
|
||||
// Assert coordinates array is not empty
|
||||
expect(coordinates.length).toBeGreaterThan(0);
|
||||
|
||||
// Validate each LinearRing in the coordinates array
|
||||
// Note: Zones can have multiple rings (holes) or be MultiPolygon (disconnected components)
|
||||
for (const linearRing of coordinates) {
|
||||
// Assert LinearRing is an array
|
||||
expect(Array.isArray(linearRing)).toBe(true);
|
||||
|
||||
// Task 7.2: Validate LinearRing validity
|
||||
// Assert LinearRing has at least 4 positions
|
||||
expect(linearRing.length).toBeGreaterThanOrEqual(4);
|
||||
|
||||
// Assert first position equals last position (closed ring)
|
||||
const firstPosition = linearRing[0];
|
||||
const lastPosition = linearRing[linearRing.length - 1];
|
||||
expect(firstPosition[0]).toBe(lastPosition[0]);
|
||||
expect(firstPosition[1]).toBe(lastPosition[1]);
|
||||
|
||||
// Assert each position in LinearRing is an array of 2 numbers
|
||||
for (const position of linearRing) {
|
||||
expect(Array.isArray(position)).toBe(true);
|
||||
expect(position.length).toBe(2);
|
||||
expect(typeof position[0]).toBe("number");
|
||||
expect(typeof position[1]).toBe("number");
|
||||
|
||||
// Assert all positions are valid [longitude, latitude] pairs
|
||||
// Longitude should be between -180 and 180
|
||||
expect(position[0]).toBeGreaterThanOrEqual(-180);
|
||||
expect(position[0]).toBeLessThanOrEqual(180);
|
||||
|
||||
// Latitude should be between -90 and 90
|
||||
expect(position[1]).toBeGreaterThanOrEqual(-90);
|
||||
expect(position[1]).toBeLessThanOrEqual(90);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("should exclude hidden zones from GeoJSON export", async ({ page }) => {
|
||||
// Create a regular test zone
|
||||
const regularZoneId = await createTestZone(page);
|
||||
expect(regularZoneId).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Create a hidden zone
|
||||
const hiddenZoneId = await page.evaluate(() => {
|
||||
const { cells, zones } = (window as any).pack;
|
||||
|
||||
// Find a starting land cell that's not already in a zone
|
||||
const totalCells = cells.i.length;
|
||||
let startCell = -1;
|
||||
for (let i = 1; i < totalCells; i++) {
|
||||
const isLand = cells.h[i] >= 20;
|
||||
const notInZone = !zones.some((z: any) => z.cells && z.cells.includes(i));
|
||||
if (isLand && notInZone) {
|
||||
startCell = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startCell === -1) {
|
||||
throw new Error("No available land cells found for hidden zone");
|
||||
}
|
||||
|
||||
// Use BFS to select a contiguous set of 10-20 land cells
|
||||
const zoneCells: number[] = [];
|
||||
const visited = new Set<number>();
|
||||
const queue: number[] = [];
|
||||
|
||||
visited.add(startCell);
|
||||
queue.push(startCell);
|
||||
|
||||
while (queue.length > 0 && zoneCells.length < 20) {
|
||||
const current = queue.shift() as number;
|
||||
|
||||
// Only include land cells not already in a zone
|
||||
const isLand = cells.h[current] >= 20;
|
||||
const notInZone = !zones.some((z: any) => z.cells && z.cells.includes(current));
|
||||
if (isLand && notInZone) {
|
||||
zoneCells.push(current);
|
||||
}
|
||||
|
||||
// Explore neighbors
|
||||
const neighbors: number[] = cells.c[current] || [];
|
||||
for (const neighbor of neighbors) {
|
||||
if (neighbor && !visited.has(neighbor)) {
|
||||
visited.add(neighbor);
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneCells.length < 10) {
|
||||
throw new Error(`Not enough contiguous land cells found: ${zoneCells.length}`);
|
||||
}
|
||||
|
||||
// Generate unique zone ID
|
||||
const zoneId = zones.length;
|
||||
|
||||
// Create hidden zone object
|
||||
const zone = {
|
||||
i: zoneId,
|
||||
name: "Hidden Test Zone",
|
||||
type: "Test",
|
||||
color: "#00FF00",
|
||||
cells: zoneCells,
|
||||
hidden: true, // Mark as hidden
|
||||
};
|
||||
|
||||
// Add zone to pack.zones array
|
||||
zones.push(zone);
|
||||
|
||||
return zoneId;
|
||||
});
|
||||
expect(hiddenZoneId).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Export zones to GeoJSON
|
||||
const geoJson = await exportZonesToGeoJson(page);
|
||||
|
||||
// Validate that the regular zone is in the export
|
||||
const regularZoneFeature = geoJson.features.find((f: any) => f.properties.id === regularZoneId);
|
||||
expect(regularZoneFeature).toBeDefined();
|
||||
expect(regularZoneFeature.properties.name).toBe("Test Export Zone");
|
||||
|
||||
// Validate that the hidden zone is NOT in the export
|
||||
const hiddenZoneFeature = geoJson.features.find((f: any) => f.properties.id === hiddenZoneId);
|
||||
expect(hiddenZoneFeature).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should exclude zones with empty cells array from GeoJSON export", async ({ page }) => {
|
||||
// Create a regular test zone
|
||||
const regularZoneId = await createTestZone(page);
|
||||
expect(regularZoneId).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Create a zone with empty cells array
|
||||
const emptyZoneId = await page.evaluate(() => {
|
||||
const { zones } = (window as any).pack;
|
||||
|
||||
// Generate unique zone ID
|
||||
const zoneId = zones.length;
|
||||
|
||||
// Create zone object with empty cells array
|
||||
const zone = {
|
||||
i: zoneId,
|
||||
name: "Empty Test Zone",
|
||||
type: "Test",
|
||||
color: "#0000FF",
|
||||
cells: [], // Empty cells array
|
||||
};
|
||||
|
||||
// Add zone to pack.zones array
|
||||
zones.push(zone);
|
||||
|
||||
return zoneId;
|
||||
});
|
||||
expect(emptyZoneId).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Export zones to GeoJSON
|
||||
const geoJson = await exportZonesToGeoJson(page);
|
||||
|
||||
// Validate that the regular zone is in the export
|
||||
const regularZoneFeature = geoJson.features.find((f: any) => f.properties.id === regularZoneId);
|
||||
expect(regularZoneFeature).toBeDefined();
|
||||
expect(regularZoneFeature.properties.name).toBe("Test Export Zone");
|
||||
|
||||
// Validate that the empty zone is NOT in the export
|
||||
const emptyZoneFeature = geoJson.features.find((f: any) => f.properties.id === emptyZoneId);
|
||||
expect(emptyZoneFeature).toBeUndefined();
|
||||
});
|
||||
});
|
||||
174
tests/fixtures/demo.map
vendored
Normal file
174
tests/fixtures/demo.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue