|
Поговорим о...
|
|
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="300" height="150" viewBox="0 0 300 150"> <text x="20" y="90" font-size="48" font-family="Times New Roman" fill="black"> <tspan>О</tspan> <tspan dx="20">П</tspan> <tspan dx="20">О</tspan> <tspan dx="20">П</tspan> <tspan dx="20">О</tspan> <tspan dx="20">П</tspan> </text> </svg>
|
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="300" height="150" viewBox="0 0 300 150"> <text x="3" y="90" font-size="48" font-family="Times New Roman" fill="black">О</text> <text x="41" y="90" font-size="48" font-family="Times New Roman" fill="black">П</text> <text x="101" y="90" font-size="48" font-family="Times New Roman" fill="black">О</text> <text x="141" y="90" font-size="48" font-family="Times New Roman" fill="black">П</text> <text x="199" y="90" font-size="48" font-family="Times New Roman" fill="black">О</text> <text x="238" y="90" font-size="48" font-family="Times New Roman" fill="black">П</text> </svg>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SVG Text Fixer — конвертер кривых текстовых иконок</title> <style> * { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: #f5f7fb; color: #1e293b; } h1 { font-size: 2rem; margin-bottom: 8px; color: #0f172a; } .subtitle { color: #475569; margin-bottom: 30px; border-left: 4px solid #3b82f6; padding-left: 16px; } .container { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; } .panel { background: white; border-radius: 16px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); padding: 20px; } .panel h2 { margin-top: 0; margin-bottom: 16px; font-size: 1.25rem; display: flex; align-items: center; gap: 8px; } textarea { width: 100%; height: 300px; font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 13px; padding: 16px; border: 1px solid #e2e8f0; border-radius: 12px; resize: vertical; background: #f8fafc; white-space: pre; overflow-x: auto; } textarea:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.1); } .preview { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; min-height: 300px; display: flex; align-items: center; justify-content: center; } .preview svg { max-width: 100%; height: auto; background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .controls { display: flex; gap: 12px; margin: 20px 0; flex-wrap: wrap; } button { background: white; border: 1px solid #e2e8f0; border-radius: 40px; padding: 10px 24px; font-size: 0.95rem; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s; color: #1e293b; } button:hover { background: #f1f5f9; border-color: #94a3b8; } button.primary { background: #3b82f6; border-color: #3b82f6; color: white; } button.primary:hover { background: #2563eb; } .badge { background: #f1f5f9; border-radius: 40px; padding: 4px 12px; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 4px; } .stats { background: #f1f5f9; border-radius: 12px; padding: 12px 20px; margin-top: 20px; font-size: 0.9rem; } .example { background: #f1f5f9; border-radius: 12px; padding: 16px; margin-top: 20px; font-size: 0.9rem; } .example pre { background: #1e293b; color: #e2e8f0; padding: 12px; border-radius: 8px; overflow-x: auto; font-size: 12px; } </style> </head> <body> <h1>🛠️ SVG Text Fixer</h1> <div class="subtitle">Конвертер "испорченных" текстовых иконок с отдельными буквами в нормальный формат</div>
<div class="container"> <div class="panel"> <h2>📥 Исходный SVG</h2> <textarea id="input" placeholder="Вставьте SVG код сюда..."><?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="300px" height="150px" viewBox="0 0 300 150"> <text id="27188" x="3" y="82" font-size="42px" font-family="Times New Roman"><tspan font-size="1.4em">О</tspan></text> <text id="26673" x="41" y="80" font-size="41px" font-family="Times New Roman"><tspan font-size="1.4em">П</tspan></text> <text id="26674" x="101" y="82" font-size="42px" font-family="Times New Roman"><tspan font-size="1.4em">О</tspan></text> <text id="26675" x="141" y="81" font-size="41px" font-family="Times New Roman"><tspan font-size="1.4em">П</tspan></text> <text id="26676" x="238" y="81" font-size="41px" font-family="Times New Roman"><tspan font-size="1.4em">П</tspan></text> <text id="26677" x="199" y="82" font-size="42px" font-family="Times New Roman"><tspan font-size="1.4em">О</tspan></text> </svg></textarea> </div>
<div class="panel"> <h2>📤 Исправленный SVG</h2> <textarea id="output" readonly placeholder="Результат появится здесь..." style="background: #f1f5f9;"></textarea> </div> </div>
<div class="controls"> <button class="primary" onclick="fixSvg()"> <span>✨</span> Исправить файл </button> <button onclick="copyToClipboard()"> <span>📋</span> Копировать результат </button> <button onclick="downloadSvg()"> <span>💾</span> Скачать .svg </button> <button onclick="loadExample()"> <span>📄</span> Загрузить пример </button> <button onclick="resetAll()"> <span>🔄</span> Сбросить </button> </div>
<div class="stats" id="stats"> Статус: ожидание файла... </div>
<div class="preview" id="preview"> <!-- сюда будет вставлен превью SVG --> </div>
<div class="example"> <h3>🔍 Как это работает:</h3> <p>Конвертер определяет "испорченные" файлы по следующим признакам:</p> <ul> <li>Множество отдельных <code><text></code> элементов с буквами/цифрами</li> <li>Разные размеры шрифта и координаты Y у соседних букв</li> <li>Использование <code><tspan></code> с относительными размерами</li> </ul> <p>✅ <strong>Нормальные файлы</strong> (с целыми словами или контурами) останутся без изменений.</p> <p>📌 <strong>Пример паттерна который мы ищем:</strong> отдельные буквы О, П, А, В, С, К, Н и т.д.</p> </div>
<script> function parseSvg(svgString) { const parser = new DOMParser(); return parser.parseFromString(svgString, 'image/svg+xml'); }
function isProblematicTextElement(textEl) { // Проверяем, похож ли этот элемент на "испорченный" const hasTspan = textEl.querySelector('tspan') !== null; const textContent = textEl.textContent?.trim() || ''; // Ищем отдельные буквы (кириллица, латиница, цифры) const isSingleChar = /^[А-Яа-яA-Za-z0-9]$/.test(textContent); // Проверяем разные размеры const fontSize = textEl.getAttribute('font-size'); const hasRelativeTspan = hasTspan && textEl.querySelector('tspan[font-size*="em"]'); return isSingleChar || hasRelativeTspan; }
function groupTextElementsByLine(textElements) { // Группируем текстовые элементы по вертикали (с допуском ±5px) const groups = []; const tolerance = 5; for (const el of textElements) { const y = parseFloat(el.getAttribute('y') || '0'); let placed = false; for (const group of groups) { if (Math.abs(group.y - y) <= tolerance) { group.elements.push({el, y}); placed = true; break; } } if (!placed) { groups.push({y, elements: [{el, y}]}); } } return groups; }
function fixBrokenSvg(svgString) { try { const doc = parseSvg(svgString); const textElements = Array.from(doc.querySelectorAll('text')); // Если нет текстовых элементов - возвращаем как есть if (textElements.length === 0) { return {svg: svgString, changed: false, reason: 'Нет текстовых элементов'}; } // Проверяем, является ли файл "проблемным" const problematicElements = textElements.filter(isProblematicTextElement); // Если проблемных элементов меньше 30% от общего числа - считаем файл нормальным if (problematicElements.length < textElements.length * 0.3) { return {svg: svgString, changed: false, reason: 'Файл выглядит нормально'}; } // Группируем элементы по строкам const lineGroups = groupTextElementsByLine(problematicElements); // Для каждой группы создаем исправленный текст for (const group of lineGroups) { // Сортируем по X координате group.elements.sort((a, b) => { const xA = parseFloat(a.el.getAttribute('x') || '0'); const xB = parseFloat(b.el.getAttribute('x') || '0'); return xA - xB; }); if (group.elements.length === 0) continue; // Берем параметры от первого элемента в группе const firstEl = group.elements[0].el; const baseY = group.y; // Определяем общий font-family const fontFamily = firstEl.getAttribute('font-family') || 'Times New Roman'; // Определяем оптимальный размер шрифта (берем медиану) const fontSizes = group.elements.map(e => { const size = e.el.getAttribute('font-size'); return size ? parseFloat(size.replace('px', '')) : 42; }); fontSizes.sort((a, b) => a - b); const medianFontSize = fontSizes[Math.floor(fontSizes.length / 2)] || 42; // Собираем буквы с их X координатами const letters = []; for (const {el} of group.elements) { const tspan = el.querySelector('tspan'); let text = ''; if (tspan && tspan.textContent) { text = tspan.textContent.trim(); } else { text = el.textContent?.trim() || ''; } if (text) { const x = el.getAttribute('x'); letters.push({text, x}); } // Удаляем старый элемент el.remove(); } // Создаем новый объединенный текст const svgNs = "http://www.w3.org/2000/svg"; if (letters.length > 1) { // Если букв много, группируем их по близости X координат const groups = []; let currentGroup = []; let lastX = null; const proximityThreshold = 15; // порог для определения группы (ОП вместе) for (const letter of letters) { const currentX = parseFloat(letter.x); if (lastX === null || (currentX - lastX) < proximityThreshold) { currentGroup.push(letter); } else { if (currentGroup.length > 0) { groups.push(currentGroup); } currentGroup = [letter]; } lastX = currentX; } if (currentGroup.length > 0) { groups.push(currentGroup); } // Создаем текстовые элементы для каждой группы for (const groupLetters of groups) { const newText = doc.createElementNS(svgNs, 'text'); newText.setAttribute('x', groupLetters[0].x); newText.setAttribute('y', baseY); newText.setAttribute('font-size', medianFontSize); newText.setAttribute('font-family', fontFamily); newText.setAttribute('fill', '#000000'); let fullText = ''; for (const letter of groupLetters) { fullText += letter.text; } newText.textContent = fullText; // Вставляем перед закрывающим svg или в g const svgRoot = doc.documentElement; const g = svgRoot.querySelector('g'); if (g) { g.appendChild(newText); } else { svgRoot.appendChild(newText); } } } else if (letters.length === 1) { // Если одна буква - просто обновляем атрибуты const newText = doc.createElementNS(svgNs, 'text'); newText.setAttribute('x', letters[0].x); newText.setAttribute('y', baseY); newText.setAttribute('font-size', medianFontSize); newText.setAttribute('font-family', fontFamily); newText.setAttribute('fill', '#000000'); newText.textContent = letters[0].text; const svgRoot = doc.documentElement; const g = svgRoot.querySelector('g'); if (g) { g.appendChild(newText); } else { svgRoot.appendChild(g); } } } // Очистка от лишних атрибутов const svgRoot = doc.documentElement; svgRoot.removeAttribute('id'); svgRoot.removeAttribute('version'); // Удаляем пустые defs const defs = svgRoot.querySelector('defs'); if (defs && !defs.hasChildNodes()) { defs.remove(); } // Сериализуем обратно const serializer = new XMLSerializer(); let fixedSvg = serializer.serializeToString(doc); // Небольшая очистка fixedSvg = fixedSvg.replace(/xmlns:svg="[^"]*"/g, ''); fixedSvg = fixedSvg.replace(/\s+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/, ' xmlns="http://www.w3.org/2000/svg"'); return { svg: fixedSvg, changed: true, reason: `Исправлено: ${problematicElements.length} текстовых элементов объединено в группы` }; } catch (e) { return {svg: svgString, changed: false, reason: `Ошибка парсинга: ${e.message}`}; } }
function fixSvg() { const input = document.getElementById('input').value; if (!input.trim()) { alert('Вставьте SVG код'); return; } const result = fixBrokenSvg(input); document.getElementById('output').value = result.svg; const stats = document.getElementById('stats'); stats.innerHTML = `Статус: ${result.changed ? '✅ ' + result.reason : 'ℹ️ ' + result.reason}`; updatePreview(result.svg); }
function updatePreview(svgString) { const preview = document.getElementById('preview'); preview.innerHTML = svgString; // Добавляем немного стилей для лучшего отображения const svg = preview.querySelector('svg'); if (svg) { svg.style.maxWidth = '100%'; svg.style.height = 'auto'; svg.style.background = 'white'; } }
function copyToClipboard() { const output = document.getElementById('output'); output.select(); navigator.clipboard.writeText(output.value).then(() => { alert('Скопировано!'); }); }
function downloadSvg() { const output = document.getElementById('output').value; if (!output.trim()) return; const blob = new Blob([output], {type: 'image/svg+xml'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'fixed-icon.svg'; a.click(); URL.revokeObjectURL(url); }
function loadExample() { document.getElementById('input').value = `<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="300px" height="150px" viewBox="0 0 300 150"> <text id="27188" x="3" y="82" font-size="42px" font-family="Times New Roman"><tspan font-size="1.4em">О</tspan></text> <text id="26673" x="41" y="80" font-size="41px" font-family="Times New Roman"><tspan font-size="1.4em">П</tspan></text> <text id="26674" x="101" y="82" font-size="42px" font-family="Times New Roman"><tspan font-size="1.4em">О</tspan></text> <text id="26675" x="141" y="81" font-size="41px" font-family="Times New Roman"><tspan font-size="1.4em">П</tspan></text> <text id="26676" x="238" y="81" font-size="41px" font-family="Times New Roman"><tspan font-size="1.4em">П</tspan></text> <text id="26677" x="199" y="82" font-size="42px" font-family="Times New Roman"><tspan font-size="1.4em">О</tspan></text> </svg>`; fixSvg(); }
function resetAll() { document.getElementById('input').value = ''; document.getElementById('output').value = ''; document.getElementById('preview').innerHTML = ''; document.getElementById('stats').innerHTML = 'Статус: ожидание файла...'; }
// Инициализация с примером window.onload = function() { loadExample(); }; </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Все стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; }
.container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; }
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; }
.section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; }
.section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; }
input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; }
textarea { min-height: 100px; resize: vertical; }
button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; }
button:hover { background-color: #2980b9; }
button.secondary { background-color: #95a5a6; }
button.secondary:hover { background-color: #7f8c8d; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background-color: #3498db; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; }
.preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; }
.preview-svg svg { max-width: 100%; max-height: 100%; }
.preview-code { font-weight: bold; color: #2c3e50; }
.preview-name { color: #7f8c8d; font-size: 0.9rem; }
.hidden { display: none; }
.message { padding: 15px; border-radius: 4px; margin: 15px 0; }
.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.actions { display: flex; gap: 10px; margin-top: 20px; }
.file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; }
.file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; }
.file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; }
.file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; }
.file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; }
.file-item:last-child { border-bottom: none; }
.file-name { flex-grow: 1; }
.file-size { color: #7f8c8d; font-size: 0.8rem; }
.data-table-container { overflow-x: auto; }
.help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; }
/* Новые стили для JSON отображения */ #jsonContent { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; padding: 15px; max-height: 500px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.4; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1>
<div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div>
<div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div>
<div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div>
<div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div>
<div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div>
<div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div>
<div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div>
<div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div>
<div class="form-group"> <label>Размер patternWidth для SVG:</label> <input type="number" id="patternWidth" value="20" min="1" max="100"> <div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div> </div>
<div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div>
<div id="messageArea"></div>
<div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = [];
// Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const patternWidthInput = document.getElementById('patternWidth'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent');
// Функция для преобразования SVG в формат как в оригинале function convertSvgToOriginalFormat(svgContent) { // Создаем временный div для парсинга SVG const tempDiv = document.createElement('div'); tempDiv.innerHTML = svgContent; const svgElement = tempDiv.querySelector('svg');
if (!svgElement) return svgContent;
// Создаем SVG с атрибутами как в оригинале let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;
// Добавляем все path элементы const paths = svgElement.querySelectorAll('path'); paths.forEach(path => { const id = path.getAttribute('id') || ''; const d = path.getAttribute('d') || ''; const strokeWidth = path.getAttribute('stroke-width') || ''; const style = path.getAttribute('style') || '';
originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`; });
// Добавляем все line элементы в формате оригинала const lines = svgElement.querySelectorAll('line'); lines.forEach(line => { const id = line.getAttribute('id') || ''; const x1 = line.getAttribute('x1') || ''; const y1 = line.getAttribute('y1') || ''; const x2 = line.getAttribute('x2') || ''; const y2 = line.getAttribute('y2') || ''; const strokeWidth = line.getAttribute('stroke-width') || ''; const style = line.getAttribute('style') || '';
originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`; });
// Добавляем все ellipse элементы в формате оригинала const ellipses = svgElement.querySelectorAll('ellipse'); ellipses.forEach(ellipse => { const id = ellipse.getAttribute('id') || ''; const cx = ellipse.getAttribute('cx') || ''; const cy = ellipse.getAttribute('cy') || ''; const rx = ellipse.getAttribute('rx') || ''; const ry = ellipse.getAttribute('ry') || ''; const strokeWidth = ellipse.getAttribute('stroke-width') || ''; const fill = ellipse.getAttribute('fill') || ''; const stroke = ellipse.getAttribute('stroke') || '';
originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`; });
originalSvg += ` </g>\n</svg>\n`;
return originalSvg; }
// Функция для правильного экранирования SVG контента для XML function escapeSvgForXml(svgContent) { // Сначала преобразуем SVG в формат оригинала let convertedSvg = convertSvgToOriginalFormat(svgContent);
// Затем экранируем все специальные символы - ИСПРАВЛЕНА ОШИБКА С КАВЫЧКАМИ let escaped = convertedSvg .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''');
// Заменяем переносы строк escaped = escaped.replace(/\n/g, ' ');
return escaped; }
// Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {};
// Очистка списка файлов fileList.innerHTML = '';
// Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));
if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; }
// Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/);
if (codeMatch) { const code = codeMatch[1];
const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent };
// Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem);
// Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } });
fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); });
// Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; }
const lines = inputText.split('\n'); rockData = [];
lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } });
updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); });
// Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return;
const name = prompt('Введите название породы:'); if (!name) return;
rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null });
updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); });
// Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = '';
if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; }
rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); });
rockTableContainer.classList.remove('hidden'); }
// Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } });
updateRockTable(); updatePreview(); }
// Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); }
// Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = '';
if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; }
rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item';
let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; }
previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `;
previewContainer.appendChild(previewItem); }); }
// Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; }
const paletteName = paletteNameInput.value || 'тест_лито'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; const patternWidth = patternWidthInput.value || '20';
// Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = '';
if (rock.svgFile && svgFiles[rock.code]) { // Правильное экранирование SVG для XML svgContent = escapeSvgForXml(svgFiles[rock.code].content); }
// Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000);
// Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile;
// Точный порядок атрибутов как в оригинале return { "color": "#ffffffff", "data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; });
// Создание основного JSON объекта с фиксированными ID как в рабочем примере // corrTemplates теперь пустой массив const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [], "corrTemplatesVersion": 0 };
// Отображение JSON с правильным форматированием jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden');
// Сохранение JSON для скачивания window.generatedJson = templateJson;
showMessage('JSON шаблон успешно сгенерирован', 'success'); });
// Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; }
const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);
showMessage('Файл успешно скачан', 'success'); });
// Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;
// Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); }
// Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Все стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; }
.container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; }
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; }
.section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; }
.section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; }
input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; }
textarea { min-height: 100px; resize: vertical; }
button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; }
button:hover { background-color: #2980b9; }
button.secondary { background-color: #95a5a6; }
button.secondary:hover { background-color: #7f8c8d; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background-color: #3498db; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; }
.preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; }
.preview-svg svg { max-width: 100%; max-height: 100%; }
.preview-code { font-weight: bold; color: #2c3e50; }
.preview-name { color: #7f8c8d; font-size: 0.9rem; }
.hidden { display: none; }
.message { padding: 15px; border-radius: 4px; margin: 15px 0; }
.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
.actions { display: flex; gap: 10px; margin-top: 20px; }
.file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; }
.file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; }
.file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; }
.file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; }
.file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; }
.file-item:last-child { border-bottom: none; }
.file-name { flex-grow: 1; font-size: 0.9rem; }
.file-size { color: #7f8c8d; font-size: 0.8rem; }
.file-badge { background-color: #3498db; color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; margin-left: 8px; }
.file-badge.fixed { background-color: #27ae60; }
.data-table-container { overflow-x: auto; }
.help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; }
/* Новые стили для JSON отображения */ #jsonContent { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; padding: 15px; max-height: 500px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.4; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1>
<div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div>
<div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div>
<div class="form-group"> <label>Дополнительные настройки обработки SVG:</label> <div style="display: flex; gap: 20px; align-items: center;"> <label style="display: flex; align-items: center; gap: 5px; font-weight: normal;"> <input type="checkbox" id="fixTextSvgCheckbox" checked> Автоматически исправлять проблемные текстовые иконки </label> <label style="display: flex; align-items: center; gap: 5px; font-weight: normal;"> <input type="number" id="proximityThreshold" value="15" min="5" max="50" style="width: 60px;"> Порог группировки букв (пиксели) </label> </div> <div class="help-text">Если включено, иконки с разрозненными буквами (О, П, А и т.д.) будут автоматически объединены в группы</div> </div>
<div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div>
<div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div>
<div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div>
<div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Статус</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div>
<div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div>
<div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div>
<div class="form-group"> <label>Размер patternWidth для SVG:</label> <input type="number" id="patternWidth" value="20" min="1" max="100"> <div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div> </div>
<div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div>
<div id="messageArea"></div>
<div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // ==================== SVG FIXER ФУНКЦИИ ==================== // Эти функции автоматически исправляют проблемные текстовые иконки
/** * Парсит SVG строку в DOM документ */ function parseSvg(svgString) { const parser = new DOMParser(); return parser.parseFromString(svgString, 'image/svg+xml'); }
/** * Проверяет, является ли текстовый элемент "проблемным" */ function isProblematicTextElement(textEl) { const hasTspan = textEl.querySelector('tspan') !== null; const textContent = textEl.textContent?.trim() || ''; // Ищем отдельные буквы (кириллица, латиница, цифры) const isSingleChar = /^[А-Яа-яA-Za-z0-9]$/.test(textContent); // Проверяем разные размеры const hasRelativeTspan = hasTspan && textEl.querySelector('tspan[font-size*="em"]'); return isSingleChar || hasRelativeTspan; }
/** * Группирует текстовые элементы по вертикали (Y координате) */ function groupTextElementsByLine(textElements, tolerance = 5) { const groups = []; for (const el of textElements) { const y = parseFloat(el.getAttribute('y') || '0'); let placed = false; for (const group of groups) { if (Math.abs(group.y - y) <= tolerance) { group.elements.push({el, y}); placed = true; break; } } if (!placed) { groups.push({y, elements: [{el, y}]}); } } return groups; }
/** * Исправляет проблемный SVG файл * @param {string} svgString - исходный SVG * @param {number} proximityThreshold - порог близости для группировки букв * @returns {object} - {svg: исправленный SVG, changed: boolean, fixedCount: number} */ function fixBrokenSvg(svgString, proximityThreshold = 15) { try { const doc = parseSvg(svgString); const textElements = Array.from(doc.querySelectorAll('text')); // Если нет текстовых элементов - возвращаем как есть if (textElements.length === 0) { return {svg: svgString, changed: false, fixedCount: 0}; } // Проверяем, является ли файл "проблемным" const problematicElements = textElements.filter(isProblematicTextElement); // Если проблемных элементов мало - считаем файл нормальным if (problematicElements.length === 0 || problematicElements.length < textElements.length * 0.3) { return {svg: svgString, changed: false, fixedCount: 0}; } let fixedCount = 0; // Группируем элементы по строкам const lineGroups = groupTextElementsByLine(problematicElements); // Для каждой группы создаем исправленный текст for (const group of lineGroups) { // Сортируем по X координате group.elements.sort((a, b) => { const xA = parseFloat(a.el.getAttribute('x') || '0'); const xB = parseFloat(b.el.getAttribute('x') || '0'); return xA - xB; }); if (group.elements.length === 0) continue; // Берем параметры от первого элемента в группе const firstEl = group.elements[0].el; const baseY = group.y; // Определяем общий font-family const fontFamily = firstEl.getAttribute('font-family') || 'Times New Roman'; // Определяем оптимальный размер шрифта (берем медиану) const fontSizes = group.elements.map(e => { const size = e.el.getAttribute('font-size'); return size ? parseFloat(size.replace('px', '')) : 42; }); fontSizes.sort((a, b) => a - b); const medianFontSize = fontSizes[Math.floor(fontSizes.length / 2)] || 42; // Собираем буквы с их X координатами const letters = []; for (const {el} of group.elements) { const tspan = el.querySelector('tspan'); let text = ''; if (tspan && tspan.textContent) { text = tspan.textContent.trim(); } else { text = el.textContent?.trim() || ''; } if (text) { const x = el.getAttribute('x'); letters.push({text, x}); } // Удаляем старый элемент el.remove(); fixedCount++; } // Создаем новый объединенный текст const svgNs = "http://www.w3.org/2000/svg"; if (letters.length > 1) { // Группируем буквы по близости X координат const groups = []; let currentGroup = []; let lastX = null; for (const letter of letters) { const currentX = parseFloat(letter.x); if (lastX === null || (currentX - lastX) < proximityThreshold) { currentGroup.push(letter); } else { if (currentGroup.length > 0) { groups.push(currentGroup); } currentGroup = [letter]; } lastX = currentX; } if (currentGroup.length > 0) { groups.push(currentGroup); } // Создаем текстовые элементы для каждой группы for (const groupLetters of groups) { const newText = doc.createElementNS(svgNs, 'text'); newText.setAttribute('x', groupLetters[0].x); newText.setAttribute('y', baseY); newText.setAttribute('font-size', medianFontSize); newText.setAttribute('font-family', fontFamily); newText.setAttribute('fill', '#000000'); let fullText = ''; for (const letter of groupLetters) { fullText += letter.text; } newText.textContent = fullText; // Вставляем в корневой элемент или в g const svgRoot = doc.documentElement; const g = svgRoot.querySelector('g'); if (g) { g.appendChild(newText); } else { svgRoot.appendChild(newText); } } } else if (letters.length === 1) { // Если одна буква - просто обновляем атрибуты const newText = doc.createElementNS(svgNs, 'text'); newText.setAttribute('x', letters[0].x); newText.setAttribute('y', baseY); newText.setAttribute('font-size', medianFontSize); newText.setAttribute('font-family', fontFamily); newText.setAttribute('fill', '#000000'); newText.textContent = letters[0].text; const svgRoot = doc.documentElement; const g = svgRoot.querySelector('g'); if (g) { g.appendChild(newText); } else { svgRoot.appendChild(newText); } } } // Очистка от лишних атрибутов const svgRoot = doc.documentElement; svgRoot.removeAttribute('id'); svgRoot.removeAttribute('version'); // Удаляем пустые defs const defs = svgRoot.querySelector('defs'); if (defs && !defs.hasChildNodes()) { defs.remove(); } // Сериализуем обратно const serializer = new XMLSerializer(); let fixedSvg = serializer.serializeToString(doc); // Небольшая очистка fixedSvg = fixedSvg.replace(/xmlns:svg="[^"]*"/g, ''); fixedSvg = fixedSvg.replace(/\s+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/, ' xmlns="http://www.w3.org/2000/svg"'); return { svg: fixedSvg, changed: fixedCount > 0, fixedCount: fixedCount }; } catch (e) { console.warn('Ошибка при исправлении SVG:', e); return {svg: svgString, changed: false, fixedCount: 0}; } }
// ==================== ОСНОВНОЙ КОД ГЕНЕРАТОРА ====================
// Переменные для хранения данных let svgFiles = {}; let rockData = []; let fixedSvgCache = {}; // Кэш для исправленных SVG
// Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const patternWidthInput = document.getElementById('patternWidth'); const fixTextSvgCheckbox = document.getElementById('fixTextSvgCheckbox'); const proximityThresholdInput = document.getElementById('proximityThreshold'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent');
/** * Получает исправленную версию SVG (если включена опция) */ function getFixedSvgContent(code, originalContent) { if (fixTextSvgCheckbox.checked) { // Проверяем кэш if (!fixedSvgCache[code]) { const threshold = parseInt(proximityThresholdInput.value) || 15; const result = fixBrokenSvg(originalContent, threshold); if (result.changed) { fixedSvgCache[code] = result.svg; return { content: result.svg, wasFixed: true, fixedCount: result.fixedCount }; } else { fixedSvgCache[code] = originalContent; } } return { content: fixedSvgCache[code], wasFixed: fixedSvgCache[code] !== originalContent }; } return { content: originalContent, wasFixed: false }; }
// Функция для преобразования SVG в формат как в оригинале function convertSvgToOriginalFormat(svgContent) { // Создаем временный div для парсинга SVG const tempDiv = document.createElement('div'); tempDiv.innerHTML = svgContent; const svgElement = tempDiv.querySelector('svg');
if (!svgElement) return svgContent;
// Создаем SVG с атрибутами как в оригинале let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;
// Добавляем все path элементы const paths = svgElement.querySelectorAll('path'); paths.forEach(path => { const id = path.getAttribute('id') || ''; const d = path.getAttribute('d') || ''; const strokeWidth = path.getAttribute('stroke-width') || ''; const style = path.getAttribute('style') || '';
originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`; });
// Добавляем все line элементы в формате оригинала const lines = svgElement.querySelectorAll('line'); lines.forEach(line => { const id = line.getAttribute('id') || ''; const x1 = line.getAttribute('x1') || ''; const y1 = line.getAttribute('y1') || ''; const x2 = line.getAttribute('x2') || ''; const y2 = line.getAttribute('y2') || ''; const strokeWidth = line.getAttribute('stroke-width') || ''; const style = line.getAttribute('style') || '';
originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`; });
// Добавляем все ellipse элементы в формате оригинала const ellipses = svgElement.querySelectorAll('ellipse'); ellipses.forEach(ellipse => { const id = ellipse.getAttribute('id') || ''; const cx = ellipse.getAttribute('cx') || ''; const cy = ellipse.getAttribute('cy') || ''; const rx = ellipse.getAttribute('rx') || ''; const ry = ellipse.getAttribute('ry') || ''; const strokeWidth = ellipse.getAttribute('stroke-width') || ''; const fill = ellipse.getAttribute('fill') || ''; const stroke = ellipse.getAttribute('stroke') || '';
originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`; });
originalSvg += ` </g>\n</svg>\n`;
return originalSvg; }
// Функция для правильного экранирования SVG контента для XML function escapeSvgForXml(svgContent) { // Сначала преобразуем SVG в формат оригинала let convertedSvg = convertSvgToOriginalFormat(svgContent);
// Затем экранируем все специальные символы let escaped = convertedSvg .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''');
// Заменяем переносы строк escaped = escaped.replace(/\n/g, ' ');
return escaped; }
// Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; fixedSvgCache = {}; // Очищаем кэш при новой загрузке
// Очистка списка файлов fileList.innerHTML = '';
// Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));
if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; }
let processedCount = 0; let fixedCount = 0;
// Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/);
if (codeMatch) { const code = codeMatch[1];
const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; // Проверяем, нужно ли исправлять файл const threshold = parseInt(proximityThresholdInput.value) || 15; const fixResult = fixBrokenSvg(svgContent, threshold); let finalContent = svgContent; let wasFixed = false; if (fixTextSvgCheckbox.checked && fixResult.changed) { finalContent = fixResult.svg; wasFixed = true; fixedCount++; fixedSvgCache[code] = finalContent; } svgFiles[code] = { name: fileName, content: finalContent, originalContent: svgContent, wasFixed: wasFixed, fixedCount: fixResult.fixedCount || 0 };
// Добавление в список файлов с индикатором исправления const fileItem = document.createElement('div'); fileItem.className = 'file-item'; let statusBadge = ''; if (wasFixed) { statusBadge = `<span class="file-badge fixed">исправлено (${fixResult.fixedCount} эл.)</span>`; } else if (fixResult.changed === false && fixTextSvgCheckbox.checked) { // Проверяем, были ли проблемные элементы const doc = parseSvg(svgContent); const textElements = Array.from(doc.querySelectorAll('text')); const problematic = textElements.filter(isProblematicTextElement); if (problematic.length > 0) { statusBadge = `<span class="file-badge">проблем не обнаружено</span>`; } } fileItem.innerHTML = ` <div class="file-name">${fileName} ${statusBadge}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem);
processedCount++; if (processedCount === svgFilesList.length) { showMessage(`Загружено ${svgFilesList.length} SVG файлов. Исправлено: ${fixedCount}`, 'success'); }
// Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code, wasFixed); }; reader.readAsText(file); } else { processedCount++; // Добавляем файлы без кода в список const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName} <span class="file-badge">нет кода</span></div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); } });
fileList.classList.remove('hidden'); });
// Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; }
const lines = inputText.split('\n'); rockData = [];
lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null, wasFixed: svgFiles[code] ? svgFiles[code].wasFixed || false : false }); } } });
updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); });
// Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return;
const name = prompt('Введите название породы:'); if (!name) return;
rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null, wasFixed: svgFiles[code] ? svgFiles[code].wasFixed || false : false });
updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); });
// Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = '';
if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; }
rockData.forEach((rock, index) => { const row = document.createElement('tr'); let statusHtml = ''; if (rock.svgFile) { if (rock.wasFixed) { statusHtml = '<span style="color: #27ae60;">✓ Исправлен</span>'; } else { statusHtml = '<span style="color: #3498db;">✓ Загружен</span>'; } } else { statusHtml = '<span style="color: #e74c3c;">✗ Файл не найден</span>'; } row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td>${statusHtml}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); });
rockTableContainer.classList.remove('hidden'); }
// Обновление таблицы при наличии SVG function updateRockTableWithSvg(code, wasFixed = false) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; rock.wasFixed = wasFixed; } });
updateRockTable(); updatePreview(); }
// Удаление породы window.removeRock = function(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); };
// Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = '';
if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; }
rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item';
let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; }
previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `;
previewContainer.appendChild(previewItem); }); }
// Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; }
const paletteName = paletteNameInput.value || 'тест_лито'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; const patternWidth = patternWidthInput.value || '20';
// Подсчет статистики исправлений let fixedCount = 0; let totalSvgCount = 0;
// Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; let wasFixed = false;
if (rock.svgFile && svgFiles[rock.code]) { // Получаем финальный контент (уже исправленный или оригинальный) const fileData = svgFiles[rock.code]; svgContent = fileData.content; wasFixed = fileData.wasFixed || false; if (wasFixed) fixedCount++; totalSvgCount++; }
// Правильное экранирование SVG для XML const escapedSvg = escapeSvgForXml(svgContent);
// Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000);
// Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile;
// Точный порядок атрибутов как в оригинале return { "color": "#ffffffff", "data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${escapedSvg}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; });
// Создание основного JSON объекта const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [], "corrTemplatesVersion": 0 };
// Отображение JSON jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden');
// Сохранение JSON для скачивания window.generatedJson = templateJson;
let message = 'JSON шаблон успешно сгенерирован'; if (fixedCount > 0) { message += `. Исправлено проблемных иконок: ${fixedCount} из ${totalSvgCount}`; } showMessage(message, 'success'); });
// Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; }
const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);
showMessage('Файл успешно скачан', 'success'); });
// Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;
// Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); }
// Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html>
|
|
|
{ "color": "#ffffffff", "data": "<StyledFill id=\"1771576203293\" name=\"\" objectName=\"\" brushColor=\"#000000\" penColor=\"#000000\" group=\"\">\n <SvgFill patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?> <svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1"> <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/> <g id="layer1"> </g> </svg> \" lineWidth=\"0.4\" objectName=\"\" penWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1810 Осадочная порода.svg\"/>\n</StyledFill>\n", "desc": "Осадочная порода", "value": 1810 },
|
|
|
|
|
{ "color": "#ffffffff", "data": "<StyledFill id=\"1771577960910\" name=\"\" objectName=\"\" brushColor=\"#000000\" penColor=\"#000000\" group=\"\">\n <SvgFill patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?> <svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1"> <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/> <g id="layer1"> <text id="" x="3" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">О</text> <text id="" x="41" y="80" font-size="42px" font-family="Times New Roman" fill="#000000">П</text> <text id="" x="101" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">О</text> <text id="" x="141" y="81" font-size="42px" font-family="Times New Roman" fill="#000000">П</text> <text id="" x="199" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">О</text> <text id="" x="238" y="81" font-size="42px" font-family="Times New Roman" fill="#000000">П</text> </g> </svg> \" lineWidth=\"0.4\" objectName=\"\" penWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1810 Осадочная порода.svg\"/>\n</StyledFill>\n", "desc": "Осадочная порода", "value": 1810 }, { "color": "#ffffffff", "data": "<StyledFill id=\"1771577960613\" name=\"\" objectName=\"\" brushColor=\"#000000\" penColor=\"#000000\" group=\"\">\n <SvgFill patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?> <svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1"> <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/> <g id="layer1"> <text id="" x="0" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">T</text> <text id="" x="41" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">П</text> <text id="" x="96" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">T</text> <text id="" x="137" y="82" font-size="42px" font-family="Times New Roman" fill="#000000">П</text> <text id="" x="195" y="83" font-size="42px" font-family="Times New Roman" fill="#000000">T</text> <text id="" x="236" y="83" font-size="42px" font-family="Times New Roman" fill="#000000">П</text> </g> </svg> \" lineWidth=\"0.4\" objectName=\"\" penWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1820 Терригенная порода.svg\"/>\n</StyledFill>\n", "desc": "Терригенная порода", "value": 1820 },
|
|
|
|
|
|
|
|
|
|
|