This commit is contained in:
2025-11-21 01:59:11 +08:00
parent 6a156d1444
commit f33c00a650
183 changed files with 527 additions and 38818 deletions
+345 -127
View File
@@ -46,7 +46,15 @@ if (!$book) {
$_SESSION['error'] = "Книга не найдена";
redirect('books.php');
}
// Получаем информацию об авторе
$author_info = null;
if ($book) {
$stmt = $pdo->prepare("SELECT display_name, username FROM users WHERE id = ?");
$stmt->execute([$book['user_id']]);
$author_info = $stmt->fetch(PDO::FETCH_ASSOC);
}
$author_name = $author_info['display_name'] ?? $author_info['username'] ?? 'Неизвестный автор';
// Функция для очистки имени файла
// function cleanFilename($filename) {
// return preg_replace('/[^a-zA-Z0-9_\-]/', '_', $filename);
@@ -57,7 +65,104 @@ function markdownToPlainText($markdown) {
// Обрабатываем диалоги (заменяем - на —)
$markdown = preg_replace('/^- (.+)$/m', "— $1", $markdown);
// Убираем Markdown разметку
// Убираем Markdown разметку, но сохраняем переносы строк
$text = $markdown;
// Убираем заголовки
$text = preg_replace('/^#+\s+/m', '', $text);
// Убираем жирный и курсив
$text = preg_replace('/\*\*(.*?)\*\*/', '$1', $text);
$text = preg_replace('/\*(.*?)\*/', '$1', $text);
$text = preg_replace('/__(.*?)__/', '$1', $text);
$text = preg_replace('/_(.*?)_/', '$1', $text);
// Убираем зачеркивание
$text = preg_replace('/~~(.*?)~~/', '$1', $text);
// Убираем код (встроенный)
$text = preg_replace('/`(.*?)`/', '$1', $text);
// Убираем блоки кода (сохраняем содержимое)
$text = preg_replace('/```.*?\n(.*?)```/s', '$1', $text);
// Убираем ссылки
$text = preg_replace('/\[(.*?)\]\(.*?\)/', '$1', $text);
// Обрабатываем списки - заменяем маркеры на *
$text = preg_replace('/^[\*\-+]\s+/m', '* ', $text);
$text = preg_replace('/^\d+\.\s+/m', '* ', $text);
// Обрабатываем цитаты
$text = preg_replace('/^>\s+/m', '', $text);
return $text;
}
// Улучшенная функция для разбивки Markdown на абзацы с сохранением структуры
function markdownToParagraphs($markdown) {
// Нормализуем переносы строк
$text = str_replace(["\r\n", "\r"], "\n", $markdown);
// Обрабатываем диалоги (заменяем - на —)
$text = preg_replace('/^- (.+)$/m', "— $1", $text);
// Разбиваем на строки
$lines = explode("\n", $text);
$paragraphs = [];
$currentParagraph = '';
foreach ($lines as $line) {
$trimmedLine = trim($line);
// Пустая строка - конец абзаца
if (empty($trimmedLine)) {
if (!empty($currentParagraph)) {
$paragraphs[] = $currentParagraph;
$currentParagraph = '';
}
continue;
}
// Диалог (начинается с —) всегда начинает новый абзац
if (str_starts_with($trimmedLine, '—')) {
if (!empty($currentParagraph)) {
$paragraphs[] = $currentParagraph;
}
$currentParagraph = $trimmedLine;
$paragraphs[] = $currentParagraph;
$currentParagraph = '';
continue;
}
// Заголовки (начинаются с #) всегда начинают новый абзац
if (str_starts_with($trimmedLine, '#')) {
if (!empty($currentParagraph)) {
$paragraphs[] = $currentParagraph;
}
$currentParagraph = preg_replace('/^#+\s+/', '', $trimmedLine);
$paragraphs[] = $currentParagraph;
$currentParagraph = '';
continue;
}
// Обычный текст - добавляем к текущему абзацу
if (!empty($currentParagraph)) {
$currentParagraph .= ' ' . $trimmedLine;
} else {
$currentParagraph = $trimmedLine;
}
}
// Добавляем последний абзац
if (!empty($currentParagraph)) {
$paragraphs[] = $currentParagraph;
}
return $paragraphs;
}
// Функция для очистки Markdown разметки
function cleanMarkdown($markdown) {
$text = $markdown;
// Убираем заголовки
@@ -79,7 +184,7 @@ function markdownToPlainText($markdown) {
// Убираем ссылки
$text = preg_replace('/\[(.*?)\]\(.*?\)/', '$1', $text);
// Обрабатываем списки
// Обрабатываем списки - убираем маркеры
$text = preg_replace('/^[\*\-+]\s+/m', '', $text);
$text = preg_replace('/^\d+\.\s+/m', '', $text);
@@ -129,38 +234,38 @@ function formatPlainText($text) {
// Обработка экспорта
switch ($format) {
case 'pdf':
exportPDF($book, $chapters, $is_public);
exportPDF($book, $chapters, $is_public, $author_name);
break;
case 'docx':
exportDOCX($book, $chapters, $is_public);
exportDOCX($book, $chapters, $is_public, $author_name);
break;
case 'odt':
exportODT($book, $chapters, $is_public);
exportODT($book, $chapters, $is_public, $author_name);
break;
case 'html':
exportHTML($book, $chapters, $is_public);
exportHTML($book, $chapters, $is_public, $author_name);
break;
case 'txt':
exportTXT($book, $chapters, $is_public);
exportTXT($book, $chapters, $is_public, $author_name);
break;
default:
$_SESSION['error'] = "Неверный формат экспорта";
redirect($share_token ? "view_book.php?share_token=$share_token" : "book_edit.php?id=$book_id");
}
function exportPDF($book, $chapters, $is_public) {
function exportPDF($book, $chapters, $is_public, $author_name) {
global $Parsedown;
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
// Устанавливаем метаданные документа
$pdf->SetCreator(APP_NAME);
$pdf->SetAuthor($is_public ? 'Автор' : ($_SESSION['display_name'] ?? 'Автор'));
$pdf->SetAuthor($author_name);
$pdf->SetTitle($book['title']);
$pdf->SetSubject($book['genre'] ?? '');
// Устанавливаем margins
$pdf->SetMargins(15, 20, 15);
$pdf->SetMargins(15, 25, 15);
$pdf->SetHeaderMargin(10);
$pdf->SetFooterMargin(10);
@@ -174,10 +279,30 @@ function exportPDF($book, $chapters, $is_public) {
$pdf->SetFont('dejavusans', '', 12);
// Заголовок книги
$pdf->SetFont('dejavusans', 'B', 16);
$pdf->SetFont('dejavusans', 'B', 18);
$pdf->Cell(0, 10, $book['title'], 0, 1, 'C');
$pdf->Ln(2);
// Автор
$pdf->SetFont('dejavusans', 'I', 14);
$pdf->Cell(0, 10, $author_name, 0, 1, 'C');
$pdf->Ln(5);
// Обложка книги
if (!empty($book['cover_image'])) {
$cover_path = COVERS_PATH . $book['cover_image'];
if (file_exists($cover_path)) {
list($width, $height) = getimagesize($cover_path);
$max_width = 80;
$ratio = $width / $height;
$new_height = $max_width / $ratio;
$x = (210 - $max_width) / 2;
$pdf->Image($cover_path, $x, $pdf->GetY(), $max_width, $new_height, '', '', 'N', false, 300, '', false, false, 0, false, false, false);
$pdf->Ln($new_height + 5);
}
}
// Жанр
if (!empty($book['genre'])) {
$pdf->SetFont('dejavusans', 'I', 12);
@@ -187,8 +312,27 @@ function exportPDF($book, $chapters, $is_public) {
// Описание
if (!empty($book['description'])) {
$pdf->SetFont('dejavusans', '', 12);
$pdf->MultiCell(0, 8, $book['description'], 0, 'J');
$pdf->SetFont('dejavusans', '', 11);
$pdf->MultiCell(0, 6, $book['description'], 0, 'J');
$pdf->Ln(10);
}
// Интерактивное оглавление - СОЗДАЕМ ССЫЛКИ
$chapterLinks = [];
if (!empty($chapters)) {
$pdf->SetFont('dejavusans', 'B', 14);
$pdf->Cell(0, 10, 'Оглавление', 0, 1, 'C');
$pdf->Ln(5);
$toc_page = $pdf->getPage();
$pdf->SetFont('dejavusans', '', 11);
foreach ($chapters as $index => $chapter) {
$chapter_number = $index + 1;
$link = $pdf->AddLink();
$chapterLinks[$chapter['id']] = $link; // Сохраняем ссылку для этой главы
$pdf->Cell(0, 6, "{$chapter_number}. {$chapter['title']}", 0, 1, 'L', false, $link);
}
$pdf->Ln(10);
}
@@ -196,34 +340,37 @@ function exportPDF($book, $chapters, $is_public) {
$pdf->Line(15, $pdf->GetY(), 195, $pdf->GetY());
$pdf->Ln(10);
// Главы
// Главы с закладками и правильными ссылками
foreach ($chapters as $index => $chapter) {
$pdf->SetFont('dejavusans', '', 12);
// Добавляем новую страницу для каждой главы
$pdf->AddPage();
// УСТАНАВЛИВАЕМ ЯКОРЬ ДЛЯ ССЫЛКИ
if (isset($chapterLinks[$chapter['id']])) {
$pdf->SetLink($chapterLinks[$chapter['id']]);
}
// Устанавливаем закладку для этой главы
$pdf->Bookmark($chapter['title'], 0, 0, '', 'B', array(0, 0, 0));
// Название главы
$pdf->SetFont('dejavusans', 'B', 14);
$pdf->Cell(0, 8, $chapter['title'], 0, 1);
$pdf->Ln(2);
// Контент главы (форматированный HTML)
// Контент главы
$pdf->SetFont('dejavusans', '', 11);
$htmlContent = $Parsedown->text($chapter['content']);
$pdf->writeHTML($htmlContent, true, false, true, false, '');
$pdf->Ln(8);
// Разделитель между главами (кроме последней)
if ($index < count($chapters) - 1) {
$pdf->Line(15, $pdf->GetY(), 195, $pdf->GetY());
$pdf->Ln(8);
}
}
// Футер с информацией
$pdf->SetY(-25);
$pdf->SetFont('dejavusans', 'I', 8);
$pdf->Cell(0, 6, 'Экспортировано из ' . APP_NAME . ' - ' . date('d.m.Y H:i'), 0, 1, 'C');
$pdf->Cell(0, 6, 'Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), 0, 1, 'C');
$pdf->Cell(0, 6, 'Автор: ' . $author_name . ' | Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), 0, 1, 'C');
// Отправляем файл
$filename = cleanFilename($book['title']) . '.pdf';
@@ -231,7 +378,7 @@ function exportPDF($book, $chapters, $is_public) {
exit;
}
function exportDOCX($book, $chapters, $is_public) {
function exportDOCX($book, $chapters, $is_public, $author_name) {
global $Parsedown;
$phpWord = new PhpWord();
@@ -245,8 +392,25 @@ function exportDOCX($book, $chapters, $is_public) {
// Заголовок книги
$section->addText($book['title'], ['bold' => true, 'size' => 16], ['alignment' => 'center']);
$section->addTextBreak(1);
// Автор
$section->addText($author_name, ['italic' => true, 'size' => 14], ['alignment' => 'center']);
$section->addTextBreak(2);
// Обложка книги
if (!empty($book['cover_image'])) {
$cover_path = COVERS_PATH . $book['cover_image'];
if (file_exists($cover_path)) {
$section->addImage($cover_path, [
'width' => 150,
'height' => 225,
'alignment' => 'center'
]);
$section->addTextBreak(2);
}
}
// Жанр
if (!empty($book['genre'])) {
$section->addText('Жанр: ' . $book['genre'], ['italic' => true], ['alignment' => 'center']);
@@ -255,33 +419,52 @@ function exportDOCX($book, $chapters, $is_public) {
// Описание
if (!empty($book['description'])) {
$section->addText($book['description']);
$descriptionParagraphs = markdownToParagraphs($book['description']);
foreach ($descriptionParagraphs as $paragraph) {
$section->addText($paragraph);
}
$section->addTextBreak(2);
}
// Интерактивное оглавление
if (!empty($chapters)) {
$section->addText('Оглавление', ['bold' => true, 'size' => 14], ['alignment' => 'center']);
$section->addTextBreak(1);
foreach ($chapters as $index => $chapter) {
$chapter_number = $index + 1;
// Создаем гиперссылку на заголовок главы - ИСПРАВЛЕННЫЙ СИНТАКСИС
$section->addLink("chapter_{$chapter['id']}", "{$chapter_number}. {$chapter['title']}", null, null, true);
$section->addTextBreak(1);
}
$section->addTextBreak(2);
}
// Разделитель
$section->addText('СОДЕРЖАНИЕ', ['bold' => true, 'size' => 14], ['alignment' => 'center']);
$section->addTextBreak(2);
$section->addPageBreak();
// Главы
// Главы с закладками - ДОБАВЛЯЕМ ПРАВИЛЬНЫЕ ЗАКЛАДКИ
foreach ($chapters as $index => $chapter) {
// Добавляем закладку для главы ПЕРЕД заголовком
$section->addBookmark("chapter_{$chapter['id']}");
// Заголовок главы
$section->addText($chapter['title'], ['bold' => true, 'size' => 14]);
$section->addTextBreak(1);
// Контент главы (форматированный HTML)
$htmlContent = $Parsedown->text($chapter['content']);
// Упрощенное добавление HTML контента
$plainContent = strip_tags($htmlContent);
$paragraphs = explode("\n\n", $plainContent);
// Получаем очищенный текст и разбиваем на абзацы
$cleanContent = cleanMarkdown($chapter['content']);
$paragraphs = markdownToParagraphs($cleanContent);
// Добавляем каждый абзац
foreach ($paragraphs as $paragraph) {
if (trim($paragraph)) {
if (!empty(trim($paragraph))) {
$section->addText($paragraph);
$section->addTextBreak(1);
}
}
// Разрыв страницы между главами (кроме последней)
// Добавляем разрыв страницы между главами (кроме последней)
if ($index < count($chapters) - 1) {
$section->addPageBreak();
}
@@ -290,7 +473,7 @@ function exportDOCX($book, $chapters, $is_public) {
// Футер
$section->addTextBreak(2);
$section->addText('Экспортировано из ' . APP_NAME . ' - ' . date('d.m.Y H:i'), ['italic' => true, 'size' => 9]);
$section->addText('Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), ['italic' => true, 'size' => 9]);
$section->addText('Автор: ' . $author_name . ' | Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), ['italic' => true, 'size' => 9]);
// Сохраняем и отправляем
$filename = cleanFilename($book['title']) . '.docx';
@@ -302,69 +485,7 @@ function exportDOCX($book, $chapters, $is_public) {
exit;
}
function exportODT($book, $chapters, $is_public) {
global $Parsedown;
$phpWord = new PhpWord();
// Стили документа
$phpWord->setDefaultFontName('Liberation Serif');
$phpWord->setDefaultFontSize(12);
// Секция документа
$section = $phpWord->addSection();
// Заголовок книги
$section->addText($book['title'], ['bold' => true, 'size' => 16], ['alignment' => 'center']);
$section->addTextBreak(2);
// Жанр
if (!empty($book['genre'])) {
$section->addText('Жанр: ' . $book['genre'], ['italic' => true], ['alignment' => 'center']);
$section->addTextBreak(1);
}
// Описание
if (!empty($book['description'])) {
$section->addText($book['description']);
$section->addTextBreak(2);
}
// Главы
foreach ($chapters as $index => $chapter) {
// Заголовок главы
$section->addText($chapter['title'], ['bold' => true, 'size' => 14]);
// Контент главы (форматированный HTML)
$htmlContent = $Parsedown->text($chapter['content']);
$plainContent = strip_tags($htmlContent);
$paragraphs = explode("\n\n", $plainContent);
foreach ($paragraphs as $paragraph) {
if (trim($paragraph)) {
$section->addText($paragraph);
}
}
$section->addTextBreak(2);
}
// Футер
$section->addTextBreak(2);
$section->addText('Экспортировано из ' . APP_NAME . ' - ' . date('d.m.Y H:i'), ['italic' => true, 'size' => 9]);
$section->addText('Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), ['italic' => true, 'size' => 9]);
// Сохраняем и отправляем
$filename = cleanFilename($book['title']) . '.odt';
header('Content-Type: application/vnd.oasis.opendocument.text');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$objWriter = IOFactory::createWriter($phpWord, 'ODText');
$objWriter->save('php://output');
exit;
}
function exportHTML($book, $chapters, $is_public) {
function exportHTML($book, $chapters, $is_public, $author_name) {
global $Parsedown;
$html = '<!DOCTYPE html>
@@ -378,7 +499,7 @@ function exportHTML($book, $chapters, $is_public) {
font-family: "Times New Roman", serif;
line-height: 1.6;
margin: 40px;
max-width: 800px;
max-width: 900px;
margin-left: auto;
margin-right: auto;
color: #333;
@@ -387,7 +508,24 @@ function exportHTML($book, $chapters, $is_public) {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
margin-bottom: 5px;
}
.book-author {
text-align: center;
font-size: 18px;
font-style: italic;
color: #666;
margin-bottom: 20px;
}
.book-cover {
text-align: center;
margin: 20px 0;
}
.book-cover img {
max-width: 200px;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.book-genre {
text-align: center;
@@ -402,11 +540,41 @@ function exportHTML($book, $chapters, $is_public) {
margin: 20px 0;
border-left: 4px solid #007bff;
}
.table-of-contents {
background: #f8f9fa;
padding: 20px;
border-radius: 5px;
margin: 20px 0;
columns: 2;
column-gap: 2rem;
}
.table-of-contents h3 {
margin-top: 0;
text-align: center;
column-span: all;
}
.table-of-contents ul {
list-style-type: none;
padding-left: 0;
}
.table-of-contents li {
margin-bottom: 5px;
padding: 5px 0;
break-inside: avoid;
}
.table-of-contents a {
text-decoration: none;
color: #333;
}
.table-of-contents a:hover {
color: #007bff;
}
.chapter-title {
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
margin-top: 30px;
font-size: 20px;
scroll-margin-top: 2rem;
}
.chapter-content {
margin: 20px 0;
@@ -420,13 +588,22 @@ function exportHTML($book, $chapters, $is_public) {
font-size: 12px;
color: #666;
}
/* Улучшаем отображение абзацев */
.chapter-content p {
margin-bottom: 1em;
text-align: justify;
}
.dialogue {
margin-left: 2rem;
font-style: italic;
color: #2c5aa0;
margin-bottom: 1em;
}
/* Остальные стили */
.chapter-content h1, .chapter-content h2, .chapter-content h3 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
.chapter-content p {
margin-bottom: 1em;
}
.chapter-content blockquote {
border-left: 4px solid #007bff;
padding-left: 15px;
@@ -462,29 +639,51 @@ function exportHTML($book, $chapters, $is_public) {
.chapter-content th {
background: #f5f5f5;
}
.dialogue {
margin-left: 2rem;
font-style: italic;
color: #2c5aa0;
@media (max-width: 768px) {
.table-of-contents {
columns: 1;
}
}
</style>
</head>
<body>
<div class="book-title">' . htmlspecialchars($book['title']) . '</div>';
<div class="book-title">' . htmlspecialchars($book['title']) . '</div>
<div class="book-author">' . htmlspecialchars($author_name) . '</div>';
if (!empty($book['genre'])) {
$html .= '<div class="book-genre">Жанр: ' . htmlspecialchars($book['genre']) . '</div>';
}
// Обложка книги
if (!empty($book['cover_image'])) {
$cover_url = COVERS_URL . $book['cover_image'];
$html .= '<div class="book-cover">';
$html .= '<img src="' . $cover_url . '" alt="' . htmlspecialchars($book['title']) . '">';
$html .= '</div>';
}
if (!empty($book['description'])) {
$html .= '<div class="book-description">' . nl2br(htmlspecialchars($book['description'])) . '</div>';
}
// Интерактивное оглавление
if (!empty($chapters)) {
$html .= '<div>';
$html .= '<h3>Оглавление</h3>';
$html .= '<ul>';
foreach ($chapters as $index => $chapter) {
$chapter_number = $index + 1;
$html .= '<li><a href="#chapter-' . $chapter['id'] . '">' . $chapter_number . '. ' . htmlspecialchars($chapter['title']) . '</a></li>';
}
$html .= '</ul>';
$html .= '</div>';
}
$html .= '<hr style="margin: 30px 0;">';
foreach ($chapters as $index => $chapter) {
$html .= '<div class="chapter">';
$html .= '<div class="chapter-title">' . htmlspecialchars($chapter['title']) . '</div>';
$html .= '<div class="chapter-title" id="chapter-' . $chapter['id'] . '" name="chapter-' . $chapter['id'] . '">' . htmlspecialchars($chapter['title']) . '</div>';
$htmlContent = $Parsedown->text($chapter['content']);
$html .= '<div class="chapter-content">' . $htmlContent . '</div>';
@@ -497,7 +696,7 @@ function exportHTML($book, $chapters, $is_public) {
$html .= '<div class="footer">
Экспортировано из ' . APP_NAME . ' - ' . date('d.m.Y H:i') . '<br>
Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')) . '
Автор: ' . htmlspecialchars($author_name) . ' | Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')) . '
</div>
</body>
</html>';
@@ -509,10 +708,11 @@ function exportHTML($book, $chapters, $is_public) {
exit;
}
function exportTXT($book, $chapters, $is_public) {
$content = "=" . str_repeat("=", 60) . "=\n";
$content .= str_pad($book['title'], 60, " ", STR_PAD_BOTH) . "\n";
$content .= "=" . str_repeat("=", 60) . "=\n\n";
function exportTXT($book, $chapters, $is_public, $author_name) {
$content = "=" . str_repeat("=", 80) . "=\n";
$content .= str_pad($book['title'], 80, " ", STR_PAD_BOTH) . "\n";
$content .= str_pad($author_name, 80, " ", STR_PAD_BOTH) . "\n";
$content .= "=" . str_repeat("=", 80) . "=\n\n";
if (!empty($book['genre'])) {
$content .= "Жанр: " . $book['genre'] . "\n\n";
@@ -520,29 +720,47 @@ function exportTXT($book, $chapters, $is_public) {
if (!empty($book['description'])) {
$content .= "ОПИСАНИЕ:\n";
$content .= wordwrap($book['description'], 80) . "\n\n";
// Увеличиваем ширину до 144 символов (80 * 1.8)
$content .= wordwrap($book['description'], 144) . "\n\n";
}
$content .= str_repeat("-", 80) . "\n\n";
// Оглавление
if (!empty($chapters)) {
$content .= "ОГЛАВЛЕНИЕ:\n";
$content .= str_repeat("-", 60) . "\n";
foreach ($chapters as $index => $chapter) {
$chapter_number = $index + 1;
$content .= "{$chapter_number}. {$chapter['title']}\n";
}
$content .= "\n";
}
$content .= str_repeat("-", 144) . "\n\n";
foreach ($chapters as $index => $chapter) {
$content .= $chapter['title'] . "\n";
$content .= str_repeat("-", 40) . "\n\n";
$content .= str_repeat("-", 60) . "\n\n";
// Форматируем текст для TXT
$plainText = markdownToPlainText($chapter['content']);
$formattedText = formatPlainText($plainText);
$content .= wordwrap($formattedText, 80) . "\n\n";
// Получаем очищенный текст и разбиваем на абзацы
$cleanContent = cleanMarkdown($chapter['content']);
$paragraphs = markdownToParagraphs($cleanContent);
foreach ($paragraphs as $paragraph) {
if (!empty(trim($paragraph))) {
// Увеличиваем ширину до 144 символов
$content .= wordwrap($paragraph, 144) . "\n\n";
}
}
if ($index < count($chapters) - 1) {
$content .= str_repeat("-", 80) . "\n\n";
$content .= str_repeat("-", 144) . "\n\n";
}
}
$content .= "\n" . str_repeat("=", 80) . "\n";
$content .= "\n" . str_repeat("=", 144) . "\n";
$content .= "Экспортировано из " . APP_NAME . " - " . date('d.m.Y H:i') . "\n";
$content .= "Всего глав: " . count($chapters) . " | Всего слов: " . array_sum(array_column($chapters, 'word_count')) . "\n";
$content .= str_repeat("=", 80) . "\n";
$content .= "Автор: " . $author_name . " | Всего глав: " . count($chapters) . " | Всего слов: " . array_sum(array_column($chapters, 'word_count')) . "\n";
$content .= str_repeat("=", 144) . "\n";
$filename = cleanFilename($book['title']) . '.txt';
header('Content-Type: text/plain; charset=utf-8');