487 lines
19 KiB
C++
487 lines
19 KiB
C++
|
|
#include "DocxBuilder.h"
|
||
|
|
|
||
|
|
#include <QFile>
|
||
|
|
#include <QFileInfo>
|
||
|
|
#include <QBuffer>
|
||
|
|
#include <QImage>
|
||
|
|
#include <QByteArray>
|
||
|
|
#include <QDir>
|
||
|
|
#include <QDebug>
|
||
|
|
|
||
|
|
// Qt ZIP support (входит в состав Qt5 через QtCore private или quazip).
|
||
|
|
// Здесь используем простую реализацию через QZipWriter (Qt >= 5.x, модуль QtCore/private).
|
||
|
|
// Если QZipWriter недоступен — подключите QuaZip: https://github.com/stachenov/quazip
|
||
|
|
#include <QtCore/private/qzipreader_p.h>
|
||
|
|
#include <QtCore/private/qzipwriter_p.h>
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
DocxBuilder::DocxBuilder() = default;
|
||
|
|
|
||
|
|
void DocxBuilder::clear() {
|
||
|
|
m_elements.clear();
|
||
|
|
m_images.clear();
|
||
|
|
m_imageCounter = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// Добавление элементов
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
void DocxBuilder::addHeading(const QString& text, int level) {
|
||
|
|
DocElement el;
|
||
|
|
el.text = text;
|
||
|
|
switch (level) {
|
||
|
|
case 1: el.type = DocElementType::Heading1; break;
|
||
|
|
case 2: el.type = DocElementType::Heading2; break;
|
||
|
|
default: el.type = DocElementType::Heading3; break;
|
||
|
|
}
|
||
|
|
el.style.font = "Times New Roman";
|
||
|
|
el.style.bold = true;
|
||
|
|
el.style.fontSize = (level == 1) ? 36 : (level == 2) ? 30 : 26;
|
||
|
|
m_elements.append(el);
|
||
|
|
}
|
||
|
|
|
||
|
|
void DocxBuilder::addParagraph(const QString& text, const DocStyle& style) {
|
||
|
|
DocElement el;
|
||
|
|
el.type = DocElementType::NormalText;
|
||
|
|
el.text = text;
|
||
|
|
el.style = style;
|
||
|
|
m_elements.append(el);
|
||
|
|
}
|
||
|
|
|
||
|
|
void DocxBuilder::addTable(const TableData& table) {
|
||
|
|
DocElement el;
|
||
|
|
el.type = DocElementType::Table;
|
||
|
|
el.table = table;
|
||
|
|
m_elements.append(el);
|
||
|
|
}
|
||
|
|
|
||
|
|
void DocxBuilder::addImage(const QString& imagePath, int widthEmu, int heightEmu) {
|
||
|
|
QFileInfo fi(imagePath);
|
||
|
|
if (!fi.exists()) {
|
||
|
|
qWarning() << "DocxBuilder::addImage — файл не найден:" << imagePath;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
m_imageCounter++;
|
||
|
|
ImageEntry entry;
|
||
|
|
entry.sourcePath = imagePath;
|
||
|
|
entry.ext = fi.suffix().toLower();
|
||
|
|
entry.rId = QString("rId%1").arg(10 + m_imageCounter);
|
||
|
|
entry.index = m_imageCounter;
|
||
|
|
m_images.append(entry);
|
||
|
|
|
||
|
|
DocElement el;
|
||
|
|
el.type = DocElementType::Image;
|
||
|
|
el.imagePath = imagePath;
|
||
|
|
el.imageWidthEmu = widthEmu;
|
||
|
|
el.imageHeightEmu = heightEmu;
|
||
|
|
// сохраняем индекс в fontSize (хак для передачи индекса)
|
||
|
|
el.style.fontSize = m_imageCounter;
|
||
|
|
m_elements.append(el);
|
||
|
|
}
|
||
|
|
|
||
|
|
void DocxBuilder::addPageBreak() {
|
||
|
|
DocElement el;
|
||
|
|
el.type = DocElementType::NormalText;
|
||
|
|
el.text = "\f"; // маркер разрыва страницы
|
||
|
|
m_elements.append(el);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// Сохранение
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
bool DocxBuilder::save(const QString& filePath) {
|
||
|
|
QFile file(filePath);
|
||
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
||
|
|
qWarning() << "DocxBuilder::save — не удалось открыть файл:" << filePath;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
QZipWriter zip(&file);
|
||
|
|
zip.setCompressionPolicy(QZipWriter::AutoCompress);
|
||
|
|
|
||
|
|
// --- Обязательные части DOCX ---
|
||
|
|
zip.addFile("[Content_Types].xml", buildContentTypesXml().toUtf8());
|
||
|
|
zip.addFile("_rels/.rels", buildRelsXml().toUtf8());
|
||
|
|
zip.addFile("word/document.xml", buildDocumentXml().toUtf8());
|
||
|
|
zip.addFile("word/_rels/document.xml.rels", buildDocumentRelsXml().toUtf8());
|
||
|
|
zip.addFile("word/styles.xml", buildStylesXml().toUtf8());
|
||
|
|
zip.addFile("word/settings.xml", buildSettingsXml().toUtf8());
|
||
|
|
|
||
|
|
// --- Изображения ---
|
||
|
|
for (const ImageEntry& img : m_images) {
|
||
|
|
QFile imgFile(img.sourcePath);
|
||
|
|
if (imgFile.open(QIODevice::ReadOnly)) {
|
||
|
|
QByteArray data = imgFile.readAll();
|
||
|
|
zip.addFile(QString("word/media/image%1.%2").arg(img.index).arg(img.ext), data);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
zip.close();
|
||
|
|
file.close();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// Построение XML документа
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
QString DocxBuilder::buildDocumentXml() {
|
||
|
|
QString body;
|
||
|
|
|
||
|
|
for (const DocElement& el : m_elements) {
|
||
|
|
switch (el.type) {
|
||
|
|
case DocElementType::Heading1:
|
||
|
|
body += makeParagraph(el.text, el.style, "Heading1");
|
||
|
|
break;
|
||
|
|
case DocElementType::Heading2:
|
||
|
|
body += makeParagraph(el.text, el.style, "Heading2");
|
||
|
|
break;
|
||
|
|
case DocElementType::Heading3:
|
||
|
|
body += makeParagraph(el.text, el.style, "Heading3");
|
||
|
|
break;
|
||
|
|
case DocElementType::NormalText:
|
||
|
|
if (el.text == "\f") {
|
||
|
|
// Разрыв страницы
|
||
|
|
body += R"(<w:p><w:r><w:br w:type="page"/></w:r></w:p>)";
|
||
|
|
} else {
|
||
|
|
body += makeParagraph(el.text, el.style);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case DocElementType::Table:
|
||
|
|
body += makeTable(el.table);
|
||
|
|
break;
|
||
|
|
case DocElementType::Image: {
|
||
|
|
// Найти соответствующую ImageEntry
|
||
|
|
int idx = el.style.fontSize;
|
||
|
|
ImageEntry* found = nullptr;
|
||
|
|
for (ImageEntry& img : m_images) {
|
||
|
|
if (img.index == idx) { found = &img; break; }
|
||
|
|
}
|
||
|
|
if (found) {
|
||
|
|
QString imgXml = makeImageXml(
|
||
|
|
found->rId.mid(3).toInt(),
|
||
|
|
el.imageWidthEmu, el.imageHeightEmu, idx);
|
||
|
|
body += QString("<w:p><w:r>%1</w:r></w:p>").arg(imgXml);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return QString(
|
||
|
|
R"(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>)"
|
||
|
|
R"(<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas")"
|
||
|
|
R"( xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main")"
|
||
|
|
R"( xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships")"
|
||
|
|
R"( xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing")"
|
||
|
|
R"( xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main")"
|
||
|
|
R"( xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">)"
|
||
|
|
"<w:body>%1"
|
||
|
|
R"(<w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1134" w:right="850" w:bottom="1134" w:left="1701" w:header="709" w:footer="709" w:gutter="0"/></w:sectPr>)"
|
||
|
|
"</w:body></w:document>"
|
||
|
|
).arg(body);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// Вспомогательные генераторы XML
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
QString DocxBuilder::escapeXml(const QString& text) {
|
||
|
|
QString out = text;
|
||
|
|
out.replace("&", "&");
|
||
|
|
out.replace("<", "<");
|
||
|
|
out.replace(">", ">");
|
||
|
|
out.replace("\"", """);
|
||
|
|
out.replace("'", "'");
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::makeRunProps(const DocStyle& style) {
|
||
|
|
QString rpr = "<w:rPr>";
|
||
|
|
rpr += QString("<w:rFonts w:ascii=\"%1\" w:hAnsi=\"%1\"/>").arg(style.font);
|
||
|
|
rpr += QString("<w:sz w:val=\"%1\"/>").arg(style.fontSize);
|
||
|
|
rpr += QString("<w:szCs w:val=\"%1\"/>").arg(style.fontSize);
|
||
|
|
if (style.bold) rpr += "<w:b/><w:bCs/>";
|
||
|
|
if (style.italic) rpr += "<w:i/><w:iCs/>";
|
||
|
|
rpr += "</w:rPr>";
|
||
|
|
return rpr;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::makeParaProps(const DocStyle& style, const QString& styleId) {
|
||
|
|
QString ppr = "<w:pPr>";
|
||
|
|
if (!styleId.isEmpty())
|
||
|
|
ppr += QString("<w:pStyle w:val=\"%1\"/>").arg(styleId);
|
||
|
|
if (style.center)
|
||
|
|
ppr += "<w:jc w:val=\"center\"/>";
|
||
|
|
// Отступы для обычного текста
|
||
|
|
if (styleId.isEmpty())
|
||
|
|
ppr += "<w:spacing w:line=\"276\" w:lineRule=\"auto\"/>";
|
||
|
|
ppr += "</w:pPr>";
|
||
|
|
return ppr;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::makeParagraph(const QString& text,
|
||
|
|
const DocStyle& style,
|
||
|
|
const QString& styleId)
|
||
|
|
{
|
||
|
|
// Разбиваем по переносам строки на отдельные параграфы
|
||
|
|
QStringList lines = text.split('\n');
|
||
|
|
QString result;
|
||
|
|
for (int li = 0; li < lines.size(); ++li) {
|
||
|
|
result += "<w:p>";
|
||
|
|
result += makeParaProps(style, styleId);
|
||
|
|
result += "<w:r>";
|
||
|
|
result += makeRunProps(style);
|
||
|
|
result += QString("<w:t xml:space=\"preserve\">%1</w:t>").arg(escapeXml(lines[li]));
|
||
|
|
result += "</w:r>";
|
||
|
|
result += "</w:p>";
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::makeTable(const TableData& table) {
|
||
|
|
// Считаем количество колонок
|
||
|
|
int cols = table.headers.isEmpty()
|
||
|
|
? (table.rows.isEmpty() ? 1 : table.rows[0].size())
|
||
|
|
: table.headers.size();
|
||
|
|
|
||
|
|
// Ширина колонки: таблица на всю ширину страницы A4 (9356 DXA)
|
||
|
|
int colWidth = (cols > 0) ? (9356 / cols) : 9356;
|
||
|
|
|
||
|
|
QString tbl;
|
||
|
|
tbl += "<w:tbl>";
|
||
|
|
|
||
|
|
// Свойства таблицы
|
||
|
|
tbl += "<w:tblPr>";
|
||
|
|
tbl += "<w:tblStyle w:val=\"TableGrid\"/>";
|
||
|
|
tbl += "<w:tblW w:w=\"9356\" w:type=\"dxa\"/>";
|
||
|
|
tbl += "<w:tblBorders>"
|
||
|
|
"<w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"</w:tblBorders>";
|
||
|
|
tbl += "</w:tblPr>";
|
||
|
|
|
||
|
|
// Сетка колонок
|
||
|
|
tbl += "<w:tblGrid>";
|
||
|
|
for (int c = 0; c < cols; ++c)
|
||
|
|
tbl += QString("<w:gridCol w:w=\"%1\"/>").arg(colWidth);
|
||
|
|
tbl += "</w:tblGrid>";
|
||
|
|
|
||
|
|
// Строка заголовков
|
||
|
|
if (!table.headers.isEmpty()) {
|
||
|
|
tbl += "<w:tr>";
|
||
|
|
for (const QString& hdr : table.headers) {
|
||
|
|
tbl += "<w:tc>";
|
||
|
|
tbl += QString("<w:tcPr><w:tcW w:w=\"%1\" w:type=\"dxa\"/>"
|
||
|
|
"<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"D9D9D9\"/>"
|
||
|
|
"</w:tcPr>").arg(colWidth);
|
||
|
|
tbl += "<w:p><w:pPr><w:jc w:val=\"center\"/></w:pPr>"
|
||
|
|
"<w:r><w:rPr>"
|
||
|
|
"<w:rFonts w:ascii=\"Times New Roman\" w:hAnsi=\"Times New Roman\"/>"
|
||
|
|
"<w:b/><w:sz w:val=\"24\"/><w:szCs w:val=\"24\"/>"
|
||
|
|
"</w:rPr>";
|
||
|
|
tbl += QString("<w:t>%1</w:t></w:r></w:p>").arg(escapeXml(hdr));
|
||
|
|
tbl += "</w:tc>";
|
||
|
|
}
|
||
|
|
tbl += "</w:tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Строки данных
|
||
|
|
for (const QStringList& row : table.rows) {
|
||
|
|
tbl += "<w:tr>";
|
||
|
|
for (int c = 0; c < cols; ++c) {
|
||
|
|
QString cellText = (c < row.size()) ? row[c] : "";
|
||
|
|
tbl += "<w:tc>";
|
||
|
|
tbl += QString("<w:tcPr><w:tcW w:w=\"%1\" w:type=\"dxa\"/></w:tcPr>").arg(colWidth);
|
||
|
|
tbl += "<w:p><w:r><w:rPr>"
|
||
|
|
"<w:rFonts w:ascii=\"Times New Roman\" w:hAnsi=\"Times New Roman\"/>"
|
||
|
|
"<w:sz w:val=\"24\"/><w:szCs w:val=\"24\"/>"
|
||
|
|
"</w:rPr>";
|
||
|
|
tbl += QString("<w:t xml:space=\"preserve\">%1</w:t></w:r></w:p>")
|
||
|
|
.arg(escapeXml(cellText));
|
||
|
|
tbl += "</w:tc>";
|
||
|
|
}
|
||
|
|
tbl += "</w:tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
tbl += "</w:tbl>";
|
||
|
|
// Пустой параграф после таблицы (обязателен по спецификации OOXML)
|
||
|
|
tbl += "<w:p/>";
|
||
|
|
return tbl;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::makeImageXml(int rId, int widthEmu, int heightEmu, int imgIdx) {
|
||
|
|
// EMU: 914400 = 1 дюйм
|
||
|
|
return QString(
|
||
|
|
"<w:drawing>"
|
||
|
|
"<wp:inline distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\">"
|
||
|
|
"<wp:extent cx=\"%1\" cy=\"%2\"/>"
|
||
|
|
"<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>"
|
||
|
|
"<wp:docPr id=\"%3\" name=\"Image%3\"/>"
|
||
|
|
"<wp:cNvGraphicFramePr>"
|
||
|
|
"<a:graphicFrameLocks xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\""
|
||
|
|
" noChangeAspect=\"1\"/>"
|
||
|
|
"</wp:cNvGraphicFramePr>"
|
||
|
|
"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
|
||
|
|
"<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
|
||
|
|
"<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
|
||
|
|
"<pic:nvPicPr>"
|
||
|
|
"<pic:cNvPr id=\"%3\" name=\"Image%3\"/>"
|
||
|
|
"<pic:cNvPicPr/>"
|
||
|
|
"</pic:nvPicPr>"
|
||
|
|
"<pic:blipFill>"
|
||
|
|
"<a:blip r:embed=\"rId%4\"/>"
|
||
|
|
"<a:stretch><a:fillRect/></a:stretch>"
|
||
|
|
"</pic:blipFill>"
|
||
|
|
"<pic:spPr>"
|
||
|
|
"<a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"%1\" cy=\"%2\"/></a:xfrm>"
|
||
|
|
"<a:prstGeom prst=\"rect\"><a:avLst/></a:prstGeom>"
|
||
|
|
"</pic:spPr>"
|
||
|
|
"</pic:pic>"
|
||
|
|
"</a:graphicData>"
|
||
|
|
"</a:graphic>"
|
||
|
|
"</wp:inline>"
|
||
|
|
"</w:drawing>"
|
||
|
|
).arg(widthEmu).arg(heightEmu).arg(imgIdx).arg(rId);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// Системные XML-файлы DOCX
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
QString DocxBuilder::buildContentTypesXml() {
|
||
|
|
QString ct =
|
||
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||
|
|
"<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">"
|
||
|
|
"<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>"
|
||
|
|
"<Default Extension=\"xml\" ContentType=\"application/xml\"/>"
|
||
|
|
"<Override PartName=\"/word/document.xml\""
|
||
|
|
" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/>"
|
||
|
|
"<Override PartName=\"/word/styles.xml\""
|
||
|
|
" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml\"/>"
|
||
|
|
"<Override PartName=\"/word/settings.xml\""
|
||
|
|
" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml\"/>";
|
||
|
|
|
||
|
|
for (const ImageEntry& img : m_images) {
|
||
|
|
QString mime = (img.ext == "png") ? "image/png" : "image/jpeg";
|
||
|
|
ct += QString("<Default Extension=\"%1\" ContentType=\"%2\"/>").arg(img.ext).arg(mime);
|
||
|
|
}
|
||
|
|
|
||
|
|
ct += "</Types>";
|
||
|
|
return ct;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::buildRelsXml() {
|
||
|
|
return
|
||
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||
|
|
"<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
|
||
|
|
"<Relationship Id=\"rId1\""
|
||
|
|
" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\""
|
||
|
|
" Target=\"word/document.xml\"/>"
|
||
|
|
"</Relationships>";
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::buildDocumentRelsXml() {
|
||
|
|
QString rels =
|
||
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||
|
|
"<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
|
||
|
|
"<Relationship Id=\"rId1\""
|
||
|
|
" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\""
|
||
|
|
" Target=\"styles.xml\"/>"
|
||
|
|
"<Relationship Id=\"rId2\""
|
||
|
|
" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings\""
|
||
|
|
" Target=\"settings.xml\"/>";
|
||
|
|
|
||
|
|
for (const ImageEntry& img : m_images) {
|
||
|
|
rels += QString(
|
||
|
|
"<Relationship Id=\"%1\""
|
||
|
|
" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\""
|
||
|
|
" Target=\"media/image%2.%3\"/>"
|
||
|
|
).arg(img.rId).arg(img.index).arg(img.ext);
|
||
|
|
}
|
||
|
|
|
||
|
|
rels += "</Relationships>";
|
||
|
|
return rels;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::buildSettingsXml() {
|
||
|
|
return
|
||
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||
|
|
"<w:settings xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
|
||
|
|
"<w:defaultTabStop w:val=\"720\"/>"
|
||
|
|
"<w:compat><w:compatSetting w:name=\"compatibilityMode\" w:uri=\"http://schemas.microsoft.com/office/word\" w:val=\"15\"/></w:compat>"
|
||
|
|
"</w:settings>";
|
||
|
|
}
|
||
|
|
|
||
|
|
QString DocxBuilder::buildStylesXml() {
|
||
|
|
// Определяем стили Times New Roman для заголовков и нормального текста
|
||
|
|
return
|
||
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||
|
|
"<w:styles xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
|
||
|
|
|
||
|
|
// Normal
|
||
|
|
"<w:style w:type=\"paragraph\" w:default=\"1\" w:styleId=\"Normal\">"
|
||
|
|
"<w:name w:val=\"Normal\"/>"
|
||
|
|
"<w:rPr>"
|
||
|
|
"<w:rFonts w:ascii=\"Times New Roman\" w:hAnsi=\"Times New Roman\"/>"
|
||
|
|
"<w:sz w:val=\"24\"/><w:szCs w:val=\"24\"/>"
|
||
|
|
"</w:rPr>"
|
||
|
|
"</w:style>"
|
||
|
|
|
||
|
|
// Heading 1 — Times New Roman 18pt Bold
|
||
|
|
"<w:style w:type=\"paragraph\" w:styleId=\"Heading1\">"
|
||
|
|
"<w:name w:val=\"heading 1\"/>"
|
||
|
|
"<w:basedOn w:val=\"Normal\"/>"
|
||
|
|
"<w:pPr><w:outlineLvl w:val=\"0\"/><w:spacing w:before=\"240\" w:after=\"120\"/></w:pPr>"
|
||
|
|
"<w:rPr>"
|
||
|
|
"<w:rFonts w:ascii=\"Times New Roman\" w:hAnsi=\"Times New Roman\"/>"
|
||
|
|
"<w:b/><w:sz w:val=\"36\"/><w:szCs w:val=\"36\"/>"
|
||
|
|
"</w:rPr>"
|
||
|
|
"</w:style>"
|
||
|
|
|
||
|
|
// Heading 2 — Times New Roman 15pt Bold
|
||
|
|
"<w:style w:type=\"paragraph\" w:styleId=\"Heading2\">"
|
||
|
|
"<w:name w:val=\"heading 2\"/>"
|
||
|
|
"<w:basedOn w:val=\"Normal\"/>"
|
||
|
|
"<w:pPr><w:outlineLvl w:val=\"1\"/><w:spacing w:before=\"200\" w:after=\"100\"/></w:pPr>"
|
||
|
|
"<w:rPr>"
|
||
|
|
"<w:rFonts w:ascii=\"Times New Roman\" w:hAnsi=\"Times New Roman\"/>"
|
||
|
|
"<w:b/><w:sz w:val=\"30\"/><w:szCs w:val=\"30\"/>"
|
||
|
|
"</w:rPr>"
|
||
|
|
"</w:style>"
|
||
|
|
|
||
|
|
// Heading 3 — Times New Roman 13pt Bold
|
||
|
|
"<w:style w:type=\"paragraph\" w:styleId=\"Heading3\">"
|
||
|
|
"<w:name w:val=\"heading 3\"/>"
|
||
|
|
"<w:basedOn w:val=\"Normal\"/>"
|
||
|
|
"<w:pPr><w:outlineLvl w:val=\"2\"/><w:spacing w:before=\"160\" w:after=\"80\"/></w:pPr>"
|
||
|
|
"<w:rPr>"
|
||
|
|
"<w:rFonts w:ascii=\"Times New Roman\" w:hAnsi=\"Times New Roman\"/>"
|
||
|
|
"<w:b/><w:sz w:val=\"26\"/><w:szCs w:val=\"26\"/>"
|
||
|
|
"</w:rPr>"
|
||
|
|
"</w:style>"
|
||
|
|
|
||
|
|
// TableGrid
|
||
|
|
"<w:style w:type=\"table\" w:styleId=\"TableGrid\">"
|
||
|
|
"<w:name w:val=\"Table Grid\"/>"
|
||
|
|
"<w:tblPr>"
|
||
|
|
"<w:tblBorders>"
|
||
|
|
"<w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"<w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"000000\"/>"
|
||
|
|
"</w:tblBorders>"
|
||
|
|
"</w:tblPr>"
|
||
|
|
"</w:style>"
|
||
|
|
|
||
|
|
"</w:styles>";
|
||
|
|
}
|