Отзывы и предложения к софту от AleXStam
  • Страница 3 из 5
  • «
  • 1
  • 2
  • 3
  • 4
  • 5
  • »
Поговорим о...
<?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>
Оооо
Прикрепления:
lllll_1.noext (36.8 Kb)
{
"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
},
Оооо
Прикрепления:
2178617.noext (37.9 Kb)
Р
Прикрепления:
3241045.noext (37.7 Kb)
{
"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
},
Ооо
Прикрепления:
7394239.noext (34.6 Kb)
Рррп
Прикрепления:
3253846.noext (17.7 Kb)
Ооо
Прикрепления:
0655405.noext (28.0 Kb)
Ррмм
Прикрепления:
8638895.noext (29.7 Kb)
Ррррр
Прикрепления:
ttt_2.noext (17.8 Kb)
  • Страница 3 из 5
  • «
  • 1
  • 2
  • 3
  • 4
  • 5
  • »
Поиск:
Новый ответ
Имя:
Текст сообщения: