- Removed duplicate files (index2/3/4.html, symbols.js duplicates) - Kept index4.html as the main index.html (modular version) - Removed old text-generator.js (replaced by modular version) - Fixed ID mismatch in ui-bindings.js to match HTML - Added square and circle shape support in svg-generator.js - Added legend preview with copy functionality - Removed 580 lines of obsolete text-generator v4 code from app.js - Added addTextToLegend and addStandaloneArrowToLegend to export.js Still TODO: Split large files to comply with 300 line limit - app.js: 1219 lines - styles.css: 1319 lines - symbols.js: 870 lines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1220 lines
42 KiB
JavaScript
1220 lines
42 KiB
JavaScript
// ============================================
|
|
// ANWENDUNGSLOGIK
|
|
// Gutachter Symbolbibliothek v2.0
|
|
// ============================================
|
|
|
|
// ========== GLOBALE VARIABLEN ==========
|
|
let currentFilter = 'all';
|
|
let currentSearch = '';
|
|
let selectedSymbols = new Set();
|
|
let legendItems = [];
|
|
|
|
// ========== INITIALISIERUNG ==========
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
renderSymbols();
|
|
setupEventListeners();
|
|
loadLegendFromStorage();
|
|
});
|
|
|
|
// ========== EVENT LISTENERS ==========
|
|
function setupEventListeners() {
|
|
// Suche
|
|
document.getElementById('searchInput').addEventListener('input', function(e) {
|
|
currentSearch = e.target.value.toLowerCase();
|
|
renderSymbols();
|
|
});
|
|
|
|
// Filter Pills
|
|
document.querySelectorAll('.filter-pill').forEach(pill => {
|
|
pill.addEventListener('click', function() {
|
|
document.querySelectorAll('.filter-pill').forEach(p => p.classList.remove('active'));
|
|
this.classList.add('active');
|
|
currentFilter = this.dataset.filter;
|
|
renderSymbols();
|
|
});
|
|
});
|
|
|
|
// Modal schließen
|
|
document.getElementById('legendModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
closeLegendModal();
|
|
}
|
|
});
|
|
|
|
// Escape-Taste zum Schließen
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeLegendModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
// ========== SYMBOLE RENDERN ==========
|
|
function renderSymbols() {
|
|
const container = document.getElementById('symbolGrid');
|
|
container.innerHTML = '';
|
|
|
|
let hasResults = false;
|
|
|
|
Object.keys(SYMBOLS).forEach(categoryKey => {
|
|
const category = SYMBOLS[categoryKey];
|
|
|
|
// Filter nach Kategorie
|
|
if (currentFilter !== 'all' && currentFilter !== categoryKey) {
|
|
return;
|
|
}
|
|
|
|
const filteredItems = category.items.filter(item => {
|
|
if (!currentSearch) return true;
|
|
return item.name.toLowerCase().includes(currentSearch) ||
|
|
item.tags.some(tag => tag.toLowerCase().includes(currentSearch));
|
|
});
|
|
|
|
if (filteredItems.length === 0) return;
|
|
|
|
hasResults = true;
|
|
|
|
// Kategorie-Header
|
|
const categoryHeader = document.createElement('div');
|
|
categoryHeader.className = 'category-header';
|
|
categoryHeader.innerHTML = `<span>${category.icon} ${category.name}</span><span class="category-count">${filteredItems.length} Symbole</span>`;
|
|
container.appendChild(categoryHeader);
|
|
|
|
// Symbol-Grid für diese Kategorie
|
|
const categoryGrid = document.createElement('div');
|
|
categoryGrid.className = 'category-grid';
|
|
|
|
filteredItems.forEach(item => {
|
|
const card = createSymbolCard(item, categoryKey);
|
|
categoryGrid.appendChild(card);
|
|
});
|
|
|
|
container.appendChild(categoryGrid);
|
|
});
|
|
|
|
if (!hasResults) {
|
|
container.innerHTML = '<div class="no-results">Keine Symbole gefunden. Versuchen Sie einen anderen Suchbegriff.</div>';
|
|
}
|
|
}
|
|
|
|
// ========== SYMBOL-KARTE ERSTELLEN ==========
|
|
function createSymbolCard(item, categoryKey) {
|
|
const card = document.createElement('div');
|
|
card.className = 'symbol-card';
|
|
|
|
// Spezielle Klasse für Vermessungssymbole
|
|
if (categoryKey.startsWith('vermessung_')) {
|
|
card.classList.add('vermessung');
|
|
}
|
|
|
|
const isSelected = selectedSymbols.has(item.id);
|
|
if (isSelected) {
|
|
card.classList.add('selected');
|
|
}
|
|
|
|
card.innerHTML = `
|
|
<div class="symbol-preview">${item.svg}</div>
|
|
<div class="symbol-name">${item.name}</div>
|
|
<div class="symbol-actions">
|
|
<button class="btn-action btn-copy" onclick="copyAsImage('${item.id}')" title="Als Bild kopieren (transparent)">
|
|
📋 Kopieren
|
|
</button>
|
|
<button class="btn-action btn-svg" onclick="downloadSVG('${item.id}')" title="SVG herunterladen">
|
|
⬇️ SVG
|
|
</button>
|
|
<button class="btn-action btn-png" onclick="downloadSymbolPNG('${item.id}')" title="PNG herunterladen">PNG</button>
|
|
<button class="btn-action btn-jpg" onclick="downloadSymbolJPG('${item.id}')" title="JPG herunterladen">JPG</button>
|
|
<button class="btn-action btn-dxf" onclick="downloadDXF('${item.id}')" title="DXF herunterladen">
|
|
📐 DXF
|
|
</button>
|
|
<button class="btn-action btn-legend ${isSelected ? 'active' : ''}" onclick="toggleLegendSelection('${item.id}')" title="Zur Legende hinzufügen">
|
|
📑
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
return card;
|
|
}
|
|
|
|
// ========== SYMBOL FINDEN ==========
|
|
function findSymbol(id) {
|
|
for (const categoryKey of Object.keys(SYMBOLS)) {
|
|
const item = SYMBOLS[categoryKey].items.find(i => i.id === id);
|
|
if (item) return item;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ========== BILD KOPIEREN (transparent) ==========
|
|
async function copyAsImage(id) {
|
|
const item = findSymbol(id);
|
|
if (!item) return;
|
|
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const size = 256;
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
|
|
// Transparenter Hintergrund (kein fillRect)
|
|
// ctx.fillStyle = 'white'; // Entfernt für Transparenz
|
|
// ctx.fillRect(0, 0, size, size); // Entfernt für Transparenz
|
|
|
|
const img = new Image();
|
|
const svgBlob = new Blob([item.svg], { type: 'image/svg+xml;charset=utf-8' });
|
|
const url = URL.createObjectURL(svgBlob);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
img.onload = resolve;
|
|
img.onerror = reject;
|
|
img.src = url;
|
|
});
|
|
|
|
ctx.drawImage(img, 0, 0, size, size);
|
|
URL.revokeObjectURL(url);
|
|
|
|
canvas.toBlob(async (blob) => {
|
|
try {
|
|
await navigator.clipboard.write([
|
|
new ClipboardItem({ 'image/png': blob })
|
|
]);
|
|
showNotification('Bild in Zwischenablage kopiert!');
|
|
} catch (err) {
|
|
// Fallback: Download
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = item.filename.replace('.svg', '.png');
|
|
link.click();
|
|
showNotification('PNG heruntergeladen (Kopieren nicht unterstützt)');
|
|
}
|
|
}, 'image/png');
|
|
} catch (err) {
|
|
console.error('Fehler beim Bild-Export:', err);
|
|
showNotification('Fehler beim Kopieren', 'error');
|
|
}
|
|
}
|
|
|
|
// ========== SVG DOWNLOAD ==========
|
|
function downloadSVG(id) {
|
|
const item = findSymbol(id);
|
|
if (!item) return;
|
|
|
|
const blob = new Blob([item.svg], { type: 'image/svg+xml' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = item.filename;
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
showNotification('SVG heruntergeladen!');
|
|
}
|
|
|
|
// ========== DXF EXPORT (AutoCAD R12 kompatibel) ==========
|
|
function svgToDxf(svgString, scaleFactor = 1) {
|
|
const parser = new DOMParser();
|
|
const svg = parser.parseFromString(svgString, 'image/svg+xml').documentElement;
|
|
|
|
const viewBox = svg.getAttribute('viewBox')?.split(' ').map(Number) || [0, 0, 64, 64];
|
|
const height = viewBox[3];
|
|
|
|
let entities = '';
|
|
|
|
function flipY(y) {
|
|
return (height - y) * scaleFactor;
|
|
}
|
|
|
|
function scaleX(x) {
|
|
return x * scaleFactor;
|
|
}
|
|
|
|
function processElement(el) {
|
|
const tag = el.tagName?.toLowerCase();
|
|
if (!tag) return;
|
|
|
|
switch(tag) {
|
|
case 'line':
|
|
const x1 = parseFloat(el.getAttribute('x1') || 0);
|
|
const y1 = parseFloat(el.getAttribute('y1') || 0);
|
|
const x2 = parseFloat(el.getAttribute('x2') || 0);
|
|
const y2 = parseFloat(el.getAttribute('y2') || 0);
|
|
entities += createDxfLine(scaleX(x1), flipY(y1), scaleX(x2), flipY(y2));
|
|
break;
|
|
|
|
case 'rect':
|
|
const rx = parseFloat(el.getAttribute('x') || 0);
|
|
const ry = parseFloat(el.getAttribute('y') || 0);
|
|
const rw = parseFloat(el.getAttribute('width') || 0);
|
|
const rh = parseFloat(el.getAttribute('height') || 0);
|
|
// Rechteck als 4 Linien
|
|
entities += createDxfLine(scaleX(rx), flipY(ry), scaleX(rx + rw), flipY(ry));
|
|
entities += createDxfLine(scaleX(rx + rw), flipY(ry), scaleX(rx + rw), flipY(ry + rh));
|
|
entities += createDxfLine(scaleX(rx + rw), flipY(ry + rh), scaleX(rx), flipY(ry + rh));
|
|
entities += createDxfLine(scaleX(rx), flipY(ry + rh), scaleX(rx), flipY(ry));
|
|
break;
|
|
|
|
case 'circle':
|
|
const cx = parseFloat(el.getAttribute('cx') || 0);
|
|
const cy = parseFloat(el.getAttribute('cy') || 0);
|
|
const r = parseFloat(el.getAttribute('r') || 0);
|
|
entities += createDxfCircle(scaleX(cx), flipY(cy), r * scaleFactor);
|
|
break;
|
|
|
|
case 'ellipse':
|
|
const ecx = parseFloat(el.getAttribute('cx') || 0);
|
|
const ecy = parseFloat(el.getAttribute('cy') || 0);
|
|
const erx = parseFloat(el.getAttribute('rx') || 0);
|
|
const ery = parseFloat(el.getAttribute('ry') || 0);
|
|
// Ellipse als Kreis approximieren (Durchschnitt)
|
|
entities += createDxfCircle(scaleX(ecx), flipY(ecy), ((erx + ery) / 2) * scaleFactor);
|
|
break;
|
|
|
|
case 'polygon':
|
|
case 'polyline':
|
|
const points = el.getAttribute('points');
|
|
if (points) {
|
|
const pts = points.trim().split(/[\s,]+/).map(Number);
|
|
for (let i = 0; i < pts.length - 2; i += 2) {
|
|
entities += createDxfLine(
|
|
scaleX(pts[i]), flipY(pts[i+1]),
|
|
scaleX(pts[i+2]), flipY(pts[i+3])
|
|
);
|
|
}
|
|
// Polygon schließen
|
|
if (tag === 'polygon' && pts.length >= 4) {
|
|
entities += createDxfLine(
|
|
scaleX(pts[pts.length-2]), flipY(pts[pts.length-1]),
|
|
scaleX(pts[0]), flipY(pts[1])
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'path':
|
|
const d = el.getAttribute('d');
|
|
if (d) {
|
|
const pathEntities = parseSvgPath(d, scaleX, flipY);
|
|
entities += pathEntities;
|
|
}
|
|
break;
|
|
|
|
case 'text':
|
|
const tx = parseFloat(el.getAttribute('x') || 0);
|
|
const ty = parseFloat(el.getAttribute('y') || 0);
|
|
const textContent = el.textContent || '';
|
|
const fontSize = parseFloat(el.getAttribute('font-size') || 10);
|
|
entities += createDxfText(scaleX(tx), flipY(ty), textContent, fontSize * scaleFactor * 0.7);
|
|
break;
|
|
|
|
case 'g':
|
|
case 'svg':
|
|
Array.from(el.children).forEach(child => processElement(child));
|
|
break;
|
|
}
|
|
}
|
|
|
|
processElement(svg);
|
|
|
|
// DXF mit AutoCAD R12 Format (AC1009) - CRLF Zeilenenden
|
|
const dxf = [
|
|
'0', 'SECTION',
|
|
'2', 'HEADER',
|
|
'9', '$ACADVER',
|
|
'1', 'AC1009',
|
|
'9', '$INSBASE',
|
|
'10', '0.0',
|
|
'20', '0.0',
|
|
'30', '0.0',
|
|
'9', '$EXTMIN',
|
|
'10', '0.0',
|
|
'20', '0.0',
|
|
'30', '0.0',
|
|
'9', '$EXTMAX',
|
|
'10', String(height * scaleFactor),
|
|
'20', String(height * scaleFactor),
|
|
'30', '0.0',
|
|
'0', 'ENDSEC',
|
|
'0', 'SECTION',
|
|
'2', 'TABLES',
|
|
'0', 'TABLE',
|
|
'2', 'LAYER',
|
|
'70', '1',
|
|
'0', 'LAYER',
|
|
'2', '0',
|
|
'70', '0',
|
|
'62', '7',
|
|
'6', 'CONTINUOUS',
|
|
'0', 'ENDTAB',
|
|
'0', 'ENDSEC',
|
|
'0', 'SECTION',
|
|
'2', 'ENTITIES',
|
|
entities,
|
|
'0', 'ENDSEC',
|
|
'0', 'EOF'
|
|
].join('\r\n');
|
|
|
|
return dxf;
|
|
}
|
|
|
|
function createDxfLine(x1, y1, x2, y2) {
|
|
return [
|
|
'0', 'LINE',
|
|
'8', '0',
|
|
'10', x1.toFixed(4),
|
|
'20', y1.toFixed(4),
|
|
'30', '0.0',
|
|
'11', x2.toFixed(4),
|
|
'21', y2.toFixed(4),
|
|
'31', '0.0',
|
|
''
|
|
].join('\r\n');
|
|
}
|
|
|
|
function createDxfCircle(cx, cy, r) {
|
|
return [
|
|
'0', 'CIRCLE',
|
|
'8', '0',
|
|
'10', cx.toFixed(4),
|
|
'20', cy.toFixed(4),
|
|
'30', '0.0',
|
|
'40', r.toFixed(4),
|
|
''
|
|
].join('\r\n');
|
|
}
|
|
|
|
function createDxfText(x, y, text, height) {
|
|
return [
|
|
'0', 'TEXT',
|
|
'8', '0',
|
|
'10', x.toFixed(4),
|
|
'20', y.toFixed(4),
|
|
'30', '0.0',
|
|
'40', height.toFixed(4),
|
|
'1', text,
|
|
''
|
|
].join('\r\n');
|
|
}
|
|
|
|
function downloadDXF(id) {
|
|
const item = findSymbol(id);
|
|
if (!item) return;
|
|
|
|
const dxf = svgToDxf(item.dxfSvg || item.svg, 1);
|
|
const blob = new Blob([dxf], { type: 'application/dxf' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = item.filename.replace('.svg', '.dxf');
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
showNotification('DXF heruntergeladen!');
|
|
}
|
|
|
|
// ========== LEGENDE FUNKTIONEN ==========
|
|
function toggleLegendSelection(id) {
|
|
if (selectedSymbols.has(id)) {
|
|
selectedSymbols.delete(id);
|
|
} else {
|
|
selectedSymbols.add(id);
|
|
|
|
// Zur Legende hinzufügen
|
|
const item = findSymbol(id);
|
|
if (item && !legendItems.find(l => l.id === id)) {
|
|
legendItems.push({
|
|
id: item.id,
|
|
name: item.name,
|
|
svg: item.svg,
|
|
description: ''
|
|
});
|
|
}
|
|
}
|
|
|
|
renderSymbols();
|
|
updateLegendCount();
|
|
saveLegendToStorage();
|
|
}
|
|
|
|
function updateLegendCount() {
|
|
const countEl = document.getElementById('legendCount');
|
|
if (countEl) {
|
|
countEl.textContent = legendItems.length;
|
|
}
|
|
}
|
|
|
|
function openLegendModal() {
|
|
const modal = document.getElementById('legendModal');
|
|
modal.classList.add('active');
|
|
renderLegendEditor();
|
|
updateLegendPreview();
|
|
}
|
|
|
|
function closeLegendModal() {
|
|
const modal = document.getElementById('legendModal');
|
|
modal.classList.remove('active');
|
|
}
|
|
|
|
function renderLegendEditor() {
|
|
const container = document.getElementById('legendItems');
|
|
|
|
if (legendItems.length === 0) {
|
|
container.innerHTML = '<div class="legend-empty">Keine Symbole in der Legende. Klicken Sie auf 📑 bei einem Symbol, um es hinzuzufügen.</div>';
|
|
updateLegendPreview();
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = legendItems.map((item, index) => `
|
|
<div class="legend-item" data-index="${index}">
|
|
<div class="legend-item-preview">${item.svg}</div>
|
|
<div class="legend-item-content">
|
|
<input type="text" class="legend-item-name" value="${item.name}"
|
|
oninput="updateLegendItem(${index}, 'name', this.value)" placeholder="Name">
|
|
<input type="text" class="legend-item-desc" value="${item.description || ''}"
|
|
oninput="updateLegendItem(${index}, 'description', this.value)" placeholder="Beschreibung (optional)">
|
|
</div>
|
|
<div class="legend-item-actions">
|
|
<button type="button" onclick="moveLegendItem(${index}, -1)" title="Nach oben" ${index === 0 ? 'disabled' : ''}>▲</button>
|
|
<button type="button" onclick="moveLegendItem(${index}, 1)" title="Nach unten" ${index === legendItems.length - 1 ? 'disabled' : ''}>▼</button>
|
|
<button type="button" onclick="removeLegendItem(${index})" title="Entfernen" class="btn-remove">✕</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
updateLegendPreview();
|
|
}
|
|
|
|
function updateLegendItem(index, field, value) {
|
|
if (legendItems[index]) {
|
|
legendItems[index][field] = value;
|
|
saveLegendToStorage();
|
|
updateLegendPreview();
|
|
}
|
|
}
|
|
|
|
function moveLegendItem(index, direction) {
|
|
const newIndex = index + direction;
|
|
if (newIndex >= 0 && newIndex < legendItems.length) {
|
|
const temp = legendItems[index];
|
|
legendItems[index] = legendItems[newIndex];
|
|
legendItems[newIndex] = temp;
|
|
renderLegendEditor();
|
|
saveLegendToStorage();
|
|
}
|
|
}
|
|
|
|
function removeLegendItem(index) {
|
|
const item = legendItems[index];
|
|
if (item) {
|
|
selectedSymbols.delete(item.id);
|
|
}
|
|
legendItems.splice(index, 1);
|
|
renderLegendEditor();
|
|
renderSymbols();
|
|
updateLegendCount();
|
|
saveLegendToStorage();
|
|
}
|
|
|
|
function clearLegend() {
|
|
if (confirm('Möchten Sie die Legende wirklich leeren?')) {
|
|
legendItems = [];
|
|
selectedSymbols.clear();
|
|
renderLegendEditor();
|
|
renderSymbols();
|
|
updateLegendCount();
|
|
saveLegendToStorage();
|
|
}
|
|
}
|
|
|
|
// ========== LEGENDE SPEICHERN/LADEN ==========
|
|
function saveLegendToStorage() {
|
|
try {
|
|
localStorage.setItem('gutachter_legende', JSON.stringify(legendItems));
|
|
localStorage.setItem('gutachter_selected', JSON.stringify([...selectedSymbols]));
|
|
} catch (e) {
|
|
console.warn('LocalStorage nicht verfügbar');
|
|
}
|
|
}
|
|
|
|
function loadLegendFromStorage() {
|
|
try {
|
|
const saved = localStorage.getItem('gutachter_legende');
|
|
const savedSelected = localStorage.getItem('gutachter_selected');
|
|
|
|
if (saved) {
|
|
legendItems = JSON.parse(saved);
|
|
}
|
|
if (savedSelected) {
|
|
selectedSymbols = new Set(JSON.parse(savedSelected));
|
|
}
|
|
|
|
updateLegendCount();
|
|
} catch (e) {
|
|
console.warn('Fehler beim Laden der Legende');
|
|
}
|
|
}
|
|
|
|
// ========== LEGENDE VORSCHAU ==========
|
|
function generateLegendSVG() {
|
|
if (legendItems.length === 0) return null;
|
|
|
|
const itemHeight = 50;
|
|
const width = 320;
|
|
const height = legendItems.length * itemHeight + 60;
|
|
|
|
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
<rect width="${width}" height="${height}" fill="white"/>
|
|
<text x="20" y="30" font-family="Arial" font-size="18" font-weight="bold">Legende</text>
|
|
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="#ccc" stroke-width="1"/>`;
|
|
|
|
legendItems.forEach((item, index) => {
|
|
const y = 60 + index * itemHeight;
|
|
svg += `<g transform="translate(20, ${y})">
|
|
<g transform="scale(0.5)">${item.svg.replace(/<svg[^>]*>/, '').replace('</svg>', '')}</g>
|
|
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${escapeHtml(item.name)}</text>
|
|
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${escapeHtml(item.description)}</text>` : ''}
|
|
</g>`;
|
|
});
|
|
|
|
svg += '</svg>';
|
|
return svg;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function updateLegendPreview() {
|
|
const previewBox = document.getElementById('legendPreviewBox');
|
|
if (!previewBox) return;
|
|
|
|
if (legendItems.length === 0) {
|
|
previewBox.innerHTML = '<div class="legend-preview-empty">Keine Eintraege vorhanden</div>';
|
|
return;
|
|
}
|
|
|
|
const svg = generateLegendSVG();
|
|
if (svg) {
|
|
previewBox.innerHTML = svg;
|
|
}
|
|
}
|
|
|
|
async function copyLegendAsImage() {
|
|
if (legendItems.length === 0) {
|
|
showNotification('Legende ist leer', 'error');
|
|
return;
|
|
}
|
|
|
|
const svg = generateLegendSVG();
|
|
if (!svg) return;
|
|
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const itemHeight = 50;
|
|
const width = 320;
|
|
const height = legendItems.length * itemHeight + 60;
|
|
|
|
canvas.width = width * 2;
|
|
canvas.height = height * 2;
|
|
ctx.scale(2, 2);
|
|
ctx.fillStyle = 'white';
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
const img = new Image();
|
|
const svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
|
|
const url = URL.createObjectURL(svgBlob);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
img.onload = resolve;
|
|
img.onerror = reject;
|
|
img.src = url;
|
|
});
|
|
|
|
ctx.drawImage(img, 0, 0, width, height);
|
|
URL.revokeObjectURL(url);
|
|
|
|
canvas.toBlob(async (blob) => {
|
|
try {
|
|
await navigator.clipboard.write([
|
|
new ClipboardItem({ 'image/png': blob })
|
|
]);
|
|
showNotification('Legende in Zwischenablage kopiert!');
|
|
} catch (err) {
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = 'legende.png';
|
|
link.click();
|
|
showNotification('Legende als PNG heruntergeladen');
|
|
}
|
|
}, 'image/png');
|
|
} catch (err) {
|
|
console.error('Fehler beim Kopieren:', err);
|
|
showNotification('Fehler beim Kopieren', 'error');
|
|
}
|
|
}
|
|
|
|
// ========== LEGENDE EXPORTIEREN ==========
|
|
function exportLegendSVG() {
|
|
if (legendItems.length === 0) {
|
|
showNotification('Legende ist leer', 'error');
|
|
return;
|
|
}
|
|
|
|
const itemHeight = 50;
|
|
const width = 400;
|
|
const height = legendItems.length * itemHeight + 60;
|
|
|
|
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
<rect width="${width}" height="${height}" fill="white"/>
|
|
<text x="20" y="30" font-family="Arial" font-size="18" font-weight="bold">Legende</text>
|
|
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="#ccc" stroke-width="1"/>`;
|
|
|
|
legendItems.forEach((item, index) => {
|
|
const y = 60 + index * itemHeight;
|
|
svg += `<g transform="translate(20, ${y})">
|
|
<g transform="scale(0.5)">${item.svg.replace(/<svg[^>]*>/, '').replace('</svg>', '')}</g>
|
|
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${escapeHtml(item.name)}</text>
|
|
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${escapeHtml(item.description)}</text>` : ''}
|
|
</g>`;
|
|
});
|
|
|
|
svg += '</svg>';
|
|
|
|
const blob = new Blob([svg], { type: 'image/svg+xml' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = 'legende.svg';
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
showNotification('Legende als SVG exportiert!');
|
|
}
|
|
|
|
function exportLegendPNG() {
|
|
if (legendItems.length === 0) {
|
|
showNotification('Legende ist leer', 'error');
|
|
return;
|
|
}
|
|
|
|
const itemHeight = 50;
|
|
const width = 400;
|
|
const height = legendItems.length * itemHeight + 60;
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = width * 2;
|
|
canvas.height = height * 2;
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.scale(2, 2);
|
|
|
|
// Hintergrund
|
|
// ctx.fillStyle = 'white'; // Entfernt für Transparenz
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
// Titel
|
|
ctx.fillStyle = '#000';
|
|
ctx.font = 'bold 18px Arial';
|
|
ctx.fillText('Legende', 20, 30);
|
|
|
|
// Linie
|
|
ctx.strokeStyle = '#ccc';
|
|
ctx.beginPath();
|
|
ctx.moveTo(20, 40);
|
|
ctx.lineTo(width - 20, 40);
|
|
ctx.stroke();
|
|
|
|
// Symbole laden und zeichnen
|
|
let loadedCount = 0;
|
|
|
|
legendItems.forEach((item, index) => {
|
|
const y = 60 + index * itemHeight;
|
|
const img = new Image();
|
|
const svgBlob = new Blob([item.svg], { type: 'image/svg+xml' });
|
|
img.src = URL.createObjectURL(svgBlob);
|
|
|
|
img.onload = () => {
|
|
ctx.drawImage(img, 20, y - 5, 32, 32);
|
|
|
|
ctx.fillStyle = '#000';
|
|
ctx.font = 'bold 14px Arial';
|
|
ctx.fillText(item.name, 60, y + 15);
|
|
|
|
if (item.description) {
|
|
ctx.fillStyle = '#666';
|
|
ctx.font = '11px Arial';
|
|
ctx.fillText(item.description, 60, y + 30);
|
|
}
|
|
|
|
loadedCount++;
|
|
if (loadedCount === legendItems.length) {
|
|
canvas.toBlob(blob => {
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = 'legende.png';
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
showNotification('Legende als PNG exportiert!');
|
|
}, 'image/png');
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
// ========== ZIP EXPORT ==========
|
|
async function downloadAllAsZip() {
|
|
showNotification('ZIP wird erstellt...', 'info');
|
|
|
|
// Simple ZIP ohne externe Bibliothek
|
|
const files = [];
|
|
|
|
Object.keys(SYMBOLS).forEach(categoryKey => {
|
|
const category = SYMBOLS[categoryKey];
|
|
category.items.forEach(item => {
|
|
files.push({
|
|
name: `${categoryKey}/${item.filename}`,
|
|
content: item.svg
|
|
});
|
|
files.push({
|
|
name: `${categoryKey}/${item.filename.replace('.svg', '.dxf')}`,
|
|
content: svgToDxf(item.svg, 1)
|
|
});
|
|
});
|
|
});
|
|
|
|
// Da wir keine ZIP-Bibliothek haben, erstellen wir einen Download-Dialog
|
|
const info = `Symbolbibliothek enthält ${files.length / 2} Symbole in ${Object.keys(SYMBOLS).length} Kategorien.\n\n` +
|
|
`Für den ZIP-Download empfehlen wir, die Symbole einzeln herunterzuladen oder ` +
|
|
`das Projekt lokal mit einer ZIP-Bibliothek zu erweitern.`;
|
|
|
|
alert(info);
|
|
showNotification('ZIP-Export: Siehe Hinweis', 'info');
|
|
}
|
|
|
|
// ========== BENACHRICHTIGUNGEN ==========
|
|
function showNotification(message, type = 'success') {
|
|
// Bestehende Notification entfernen
|
|
const existing = document.querySelector('.notification');
|
|
if (existing) existing.remove();
|
|
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.textContent = message;
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.classList.add('show');
|
|
}, 10);
|
|
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 2500);
|
|
}
|
|
|
|
// ========== SVG PATH PARSER FÜR DXF ==========
|
|
function parseSvgPath(d, scaleX, flipY) {
|
|
let entities = "";
|
|
let currentX = 0, currentY = 0;
|
|
let startX = 0, startY = 0;
|
|
|
|
// Tokenize path data
|
|
const commands = d.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g) || [];
|
|
|
|
commands.forEach(cmd => {
|
|
const type = cmd[0];
|
|
const args = cmd.slice(1).trim().split(/[\s,]+/).filter(s => s).map(Number);
|
|
|
|
switch(type) {
|
|
case "M": // Absolute moveto
|
|
currentX = args[0]; currentY = args[1];
|
|
startX = currentX; startY = currentY;
|
|
// Weitere Punkt-Paare sind implizite LineTo
|
|
for(let i = 2; i < args.length; i += 2) {
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(args[i]), flipY(args[i+1]));
|
|
currentX = args[i]; currentY = args[i+1];
|
|
}
|
|
break;
|
|
case "m": // Relative moveto
|
|
currentX += args[0]; currentY += args[1];
|
|
startX = currentX; startY = currentY;
|
|
for(let i = 2; i < args.length; i += 2) {
|
|
const nx = currentX + args[i], ny = currentY + args[i+1];
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(nx), flipY(ny));
|
|
currentX = nx; currentY = ny;
|
|
}
|
|
break;
|
|
case "L": // Absolute lineto
|
|
for(let i = 0; i < args.length; i += 2) {
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(args[i]), flipY(args[i+1]));
|
|
currentX = args[i]; currentY = args[i+1];
|
|
}
|
|
break;
|
|
case "l": // Relative lineto
|
|
for(let i = 0; i < args.length; i += 2) {
|
|
const nx = currentX + args[i], ny = currentY + args[i+1];
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(nx), flipY(ny));
|
|
currentX = nx; currentY = ny;
|
|
}
|
|
break;
|
|
case "H": // Absolute horizontal
|
|
for(let i = 0; i < args.length; i++) {
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(args[i]), flipY(currentY));
|
|
currentX = args[i];
|
|
}
|
|
break;
|
|
case "h": // Relative horizontal
|
|
for(let i = 0; i < args.length; i++) {
|
|
const nx = currentX + args[i];
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(nx), flipY(currentY));
|
|
currentX = nx;
|
|
}
|
|
break;
|
|
case "V": // Absolute vertical
|
|
for(let i = 0; i < args.length; i++) {
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(currentX), flipY(args[i]));
|
|
currentY = args[i];
|
|
}
|
|
break;
|
|
case "v": // Relative vertical
|
|
for(let i = 0; i < args.length; i++) {
|
|
const ny = currentY + args[i];
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(currentX), flipY(ny));
|
|
currentY = ny;
|
|
}
|
|
break;
|
|
case "Q": // Quadratic Bezier (approximiert als Linie zum Endpunkt)
|
|
case "q":
|
|
for(let i = 0; i < args.length; i += 4) {
|
|
let ex, ey;
|
|
if(type === "Q") {
|
|
ex = args[i+2]; ey = args[i+3];
|
|
} else {
|
|
ex = currentX + args[i+2]; ey = currentY + args[i+3];
|
|
}
|
|
// Quadratische Bezier als Polyline approximieren
|
|
const cx = type === "Q" ? args[i] : currentX + args[i];
|
|
const cy = type === "Q" ? args[i+1] : currentY + args[i+1];
|
|
for(let t = 0; t <= 1; t += 0.25) {
|
|
const t2 = Math.min(t + 0.25, 1);
|
|
const x1 = (1-t)*(1-t)*currentX + 2*(1-t)*t*cx + t*t*ex;
|
|
const y1 = (1-t)*(1-t)*currentY + 2*(1-t)*t*cy + t*t*ey;
|
|
const x2 = (1-t2)*(1-t2)*currentX + 2*(1-t2)*t2*cx + t2*t2*ex;
|
|
const y2 = (1-t2)*(1-t2)*currentY + 2*(1-t2)*t2*cy + t2*t2*ey;
|
|
entities += createDxfLine(scaleX(x1), flipY(y1), scaleX(x2), flipY(y2));
|
|
}
|
|
currentX = ex; currentY = ey;
|
|
}
|
|
break;
|
|
case "C": // Cubic Bezier
|
|
case "c":
|
|
for(let i = 0; i < args.length; i += 6) {
|
|
let c1x, c1y, c2x, c2y, ex, ey;
|
|
if(type === "C") {
|
|
c1x = args[i]; c1y = args[i+1];
|
|
c2x = args[i+2]; c2y = args[i+3];
|
|
ex = args[i+4]; ey = args[i+5];
|
|
} else {
|
|
c1x = currentX + args[i]; c1y = currentY + args[i+1];
|
|
c2x = currentX + args[i+2]; c2y = currentY + args[i+3];
|
|
ex = currentX + args[i+4]; ey = currentY + args[i+5];
|
|
}
|
|
// Kubische Bezier als Polyline approximieren
|
|
for(let t = 0; t <= 1; t += 0.2) {
|
|
const t2 = Math.min(t + 0.2, 1);
|
|
const x1 = Math.pow(1-t,3)*currentX + 3*Math.pow(1-t,2)*t*c1x + 3*(1-t)*t*t*c2x + t*t*t*ex;
|
|
const y1 = Math.pow(1-t,3)*currentY + 3*Math.pow(1-t,2)*t*c1y + 3*(1-t)*t*t*c2y + t*t*t*ey;
|
|
const x2 = Math.pow(1-t2,3)*currentX + 3*Math.pow(1-t2,2)*t2*c1x + 3*(1-t2)*t2*t2*c2x + t2*t2*t2*ex;
|
|
const y2 = Math.pow(1-t2,3)*currentY + 3*Math.pow(1-t2,2)*t2*c1y + 3*(1-t2)*t2*t2*c2y + t2*t2*t2*ey;
|
|
entities += createDxfLine(scaleX(x1), flipY(y1), scaleX(x2), flipY(y2));
|
|
}
|
|
currentX = ex; currentY = ey;
|
|
}
|
|
break;
|
|
case "Z":
|
|
case "z": // Close path
|
|
if(currentX !== startX || currentY !== startY) {
|
|
entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(startX), flipY(startY));
|
|
}
|
|
currentX = startX; currentY = startY;
|
|
break;
|
|
}
|
|
});
|
|
|
|
return entities;
|
|
}
|
|
|
|
|
|
// ========== TEXT-GENERATOR UI ==========
|
|
function toggleTextGenerator() {
|
|
var generator = document.querySelector('.text-generator');
|
|
generator.classList.toggle('collapsed');
|
|
}
|
|
|
|
// Custom Symbols aus localStorage
|
|
var customSymbols = [];
|
|
|
|
// ========== CUSTOM SYMBOLS STORAGE ==========
|
|
|
|
function loadCustomSymbols() {
|
|
try {
|
|
var stored = localStorage.getItem('customSymbols');
|
|
if (stored) {
|
|
customSymbols = JSON.parse(stored);
|
|
}
|
|
} catch (e) {
|
|
console.error('Fehler beim Laden der eigenen Symbole:', e);
|
|
customSymbols = [];
|
|
}
|
|
}
|
|
|
|
function saveCustomSymbols() {
|
|
try {
|
|
localStorage.setItem('customSymbols', JSON.stringify(customSymbols));
|
|
} catch (e) {
|
|
console.error('Fehler beim Speichern der eigenen Symbole:', e);
|
|
}
|
|
}
|
|
|
|
function openSaveModal() {
|
|
var modal = document.getElementById('saveModal');
|
|
var nameInput = document.getElementById('symbolName');
|
|
var descInput = document.getElementById('symbolDescription');
|
|
|
|
var suggestedName = textGenState.text ? textGenState.text.replace(/\n/g, ' ').substring(0, 30) : '';
|
|
nameInput.value = suggestedName;
|
|
descInput.value = '';
|
|
|
|
modal.style.display = 'flex';
|
|
nameInput.focus();
|
|
}
|
|
|
|
function closeSaveModal() {
|
|
var modal = document.getElementById('saveModal');
|
|
modal.style.display = 'none';
|
|
}
|
|
|
|
function saveCustomSymbol() {
|
|
var nameInput = document.getElementById('symbolName');
|
|
var descInput = document.getElementById('symbolDescription');
|
|
|
|
var name = nameInput.value.trim();
|
|
if (!name) {
|
|
showNotification('Bitte einen Namen eingeben!', 'error');
|
|
return;
|
|
}
|
|
|
|
var svg = generateTextSVG();
|
|
|
|
var newSymbol = {
|
|
id: 'custom_' + Date.now(),
|
|
name: name,
|
|
description: descInput.value.trim(),
|
|
svg: svg,
|
|
category: 'custom',
|
|
tags: ['eigene', 'custom', name.toLowerCase()],
|
|
createdAt: new Date().toISOString(),
|
|
state: JSON.parse(JSON.stringify(textGenState))
|
|
};
|
|
|
|
customSymbols.push(newSymbol);
|
|
saveCustomSymbols();
|
|
|
|
closeSaveModal();
|
|
showNotification('Symbol "' + name + '" gespeichert!');
|
|
|
|
var activeFilter = document.querySelector('.filter-pill.active');
|
|
if (activeFilter && activeFilter.dataset.filter === 'custom') {
|
|
renderSymbolGrid();
|
|
}
|
|
}
|
|
|
|
function deleteCustomSymbol(id) {
|
|
if (!confirm('Symbol wirklich loeschen?')) return;
|
|
|
|
customSymbols = customSymbols.filter(function(s) { return s.id !== id; });
|
|
saveCustomSymbols();
|
|
renderSymbolGrid();
|
|
showNotification('Symbol geloescht');
|
|
}
|
|
|
|
var originalRenderSymbolGrid = typeof renderSymbolGrid === 'function' ? renderSymbolGrid : null;
|
|
|
|
function renderSymbolGridWithCustom() {
|
|
var activeFilter = document.querySelector('.filter-pill.active');
|
|
var filter = activeFilter ? activeFilter.dataset.filter : 'all';
|
|
|
|
if (filter === 'custom') {
|
|
renderCustomSymbolsOnly();
|
|
} else if (originalRenderSymbolGrid) {
|
|
originalRenderSymbolGrid();
|
|
if (filter === 'all') {
|
|
appendCustomSymbols();
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderCustomSymbolsOnly() {
|
|
var grid = document.getElementById('symbolGrid');
|
|
if (!grid) return;
|
|
|
|
grid.innerHTML = '';
|
|
|
|
if (customSymbols.length === 0) {
|
|
grid.innerHTML = '<div class="no-results">Noch keine eigenen Symbole gespeichert.<br>Erstelle ein Text-Symbol und klicke auf "Speichern".</div>';
|
|
return;
|
|
}
|
|
|
|
customSymbols.forEach(function(symbol) {
|
|
var card = createCustomSymbolCard(symbol);
|
|
grid.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function appendCustomSymbols() {
|
|
var grid = document.getElementById('symbolGrid');
|
|
if (!grid || customSymbols.length === 0) return;
|
|
|
|
customSymbols.forEach(function(symbol) {
|
|
var card = createCustomSymbolCard(symbol);
|
|
grid.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function createCustomSymbolCard(symbol) {
|
|
var card = document.createElement('div');
|
|
card.className = 'symbol-card';
|
|
card.innerHTML =
|
|
'<button class="btn-delete" onclick="event.stopPropagation(); deleteCustomSymbol(\'' + symbol.id + '\')" title="Loeschen">X</button>' +
|
|
'<div class="symbol-preview">' + symbol.svg + '</div>' +
|
|
'<div class="symbol-name">' + escapeHtml(symbol.name) + '</div>' +
|
|
'<div class="symbol-actions">' +
|
|
'<button class="btn-action btn-copy" onclick="copySymbolAsImage(\'' + symbol.id + '\', true)" title="Kopieren">Kopieren</button>' +
|
|
'<button class="btn-action btn-svg" onclick="downloadSymbolSVG(\'' + symbol.id + '\', true)" title="SVG">SVG</button>' +
|
|
'<button class="btn-action btn-dxf" onclick="downloadSymbolDXF(\'' + symbol.id + '\', true)" title="DXF">DXF</button>' +
|
|
'<button class="btn-action btn-legend" onclick="addSymbolToLegend(\'' + symbol.id + '\', true)" title="Legende">+</button>' +
|
|
'</div>';
|
|
return card;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function getSymbolById(id, isCustom) {
|
|
if (isCustom) {
|
|
return customSymbols.find(function(s) { return s.id === id; });
|
|
}
|
|
return allSymbols.find(function(s) { return s.id === id; });
|
|
}
|
|
|
|
// ========== IMPRESSUM ==========
|
|
function openImpressum() {
|
|
var modal = document.getElementById('impressumModal');
|
|
if (modal) modal.style.display = 'flex';
|
|
}
|
|
|
|
function closeImpressum() {
|
|
var modal = document.getElementById('impressumModal');
|
|
if (modal) modal.style.display = 'none';
|
|
}
|
|
|
|
// Init beim Laden
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initTextGenerator);
|
|
} else {
|
|
initTextGenerator();
|
|
}
|
|
|
|
|
|
// ========== PNG/JPG DOWNLOAD FUER ALLE SYMBOLE ==========
|
|
|
|
async function downloadSymbolPNG(id) {
|
|
var symbol = findSymbol(id);
|
|
if (!symbol) return;
|
|
|
|
var svg = symbol.svg;
|
|
try {
|
|
var canvas = await svgToCanvas(svg, 2);
|
|
canvas.toBlob(function(blob) {
|
|
var link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = symbol.id + '.png';
|
|
link.click();
|
|
URL.revokeObjectURL(link.href);
|
|
showNotification('PNG heruntergeladen!');
|
|
}, 'image/png');
|
|
} catch (err) {
|
|
console.error('Fehler:', err);
|
|
showNotification('Fehler beim PNG-Export', 'error');
|
|
}
|
|
}
|
|
|
|
async function downloadSymbolJPG(id) {
|
|
var symbol = findSymbol(id);
|
|
if (!symbol) return;
|
|
|
|
var svg = symbol.svg;
|
|
try {
|
|
var canvas = await svgToCanvas(svg, 2);
|
|
|
|
// Weisser Hintergrund fuer JPG
|
|
var tempCanvas = document.createElement('canvas');
|
|
tempCanvas.width = canvas.width;
|
|
tempCanvas.height = canvas.height;
|
|
var tempCtx = tempCanvas.getContext('2d');
|
|
tempCtx.fillStyle = '#FFFFFF';
|
|
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
|
|
tempCtx.drawImage(canvas, 0, 0);
|
|
|
|
tempCanvas.toBlob(function(blob) {
|
|
var link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = symbol.id + '.jpg';
|
|
link.click();
|
|
URL.revokeObjectURL(link.href);
|
|
showNotification('JPG heruntergeladen!');
|
|
}, 'image/jpeg', 0.95);
|
|
} catch (err) {
|
|
console.error('Fehler:', err);
|
|
showNotification('Fehler beim JPG-Export', 'error');
|
|
}
|
|
}
|
|
|
|
// SVG zu Canvas Hilfsfunktion (falls nicht vorhanden)
|
|
if (typeof svgToCanvas !== 'function') {
|
|
window.svgToCanvas = async function(svg, scale) {
|
|
var parser = new DOMParser();
|
|
var svgDoc = parser.parseFromString(svg, 'image/svg+xml');
|
|
var svgEl = svgDoc.documentElement;
|
|
var svgWidth = parseFloat(svgEl.getAttribute('width')) || parseFloat(svgEl.getAttribute('viewBox').split(' ')[2]) || 64;
|
|
var svgHeight = parseFloat(svgEl.getAttribute('height')) || parseFloat(svgEl.getAttribute('viewBox').split(' ')[3]) || 64;
|
|
|
|
if (!scale) scale = 2;
|
|
var canvasWidth = Math.round(svgWidth * scale);
|
|
var canvasHeight = Math.round(svgHeight * scale);
|
|
|
|
var canvas = document.createElement('canvas');
|
|
var ctx = canvas.getContext('2d');
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
|
|
var img = new Image();
|
|
var svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
|
|
var url = URL.createObjectURL(svgBlob);
|
|
|
|
await new Promise(function(resolve, reject) {
|
|
img.onload = resolve;
|
|
img.onerror = reject;
|
|
img.src = url;
|
|
});
|
|
|
|
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
|
|
URL.revokeObjectURL(url);
|
|
|
|
return canvas;
|
|
};
|
|
}
|