changed lib

This commit is contained in:
gre-ilya 2026-04-29 10:48:45 +05:00
parent 6dafd0eca8
commit efbda191ab
3 changed files with 279 additions and 369 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
*.swp

View File

@ -2,39 +2,117 @@
#include <QFile>
#include <QFileInfo>
#include <QBuffer>
#include <QImage>
#include <QByteArray>
#include <QDir>
#include <QSet>
#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>
// zlib входит в состав Qt и доступна на любой платформе без дополнительных зависимостей
#include <zlib.h>
// ============================================================
// Минимальный ZIP-writer без внешних библиотек
// (только zlib, которая есть везде)
// ============================================================
namespace {
quint32 crc32_buf(const QByteArray& data) {
return static_cast<quint32>(
::crc32(0, reinterpret_cast<const Bytef*>(data.constData()),
static_cast<uInt>(data.size())));
}
QByteArray deflateRaw(const QByteArray& input) {
z_stream zs{};
deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-15, 8, Z_DEFAULT_STRATEGY);
QByteArray out;
out.resize(static_cast<int>(deflateBound(&zs, static_cast<uLong>(input.size()))));
zs.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(input.constData()));
zs.avail_in = static_cast<uInt>(input.size());
zs.next_out = reinterpret_cast<Bytef*>(out.data());
zs.avail_out = static_cast<uInt>(out.size());
deflate(&zs, Z_FINISH);
out.resize(static_cast<int>(zs.total_out));
deflateEnd(&zs);
return out;
}
void u16(QByteArray& b, quint16 v) {
b += char(v & 0xFF); b += char(v >> 8);
}
void u32(QByteArray& b, quint32 v) {
b += char(v & 0xFF); b += char((v>>8)&0xFF);
b += char((v>>16)&0xFF); b += char((v>>24)&0xFF);
}
struct ZEntry { QString name; QByteArray comp, orig; quint32 crc, off; quint16 meth; };
class ZipWriter {
public:
bool open(const QString& p) {
f.setFileName(p);
return f.open(QIODevice::WriteOnly | QIODevice::Truncate);
}
void add(const QString& name, const QByteArray& data) {
ZEntry e;
e.name = name; e.orig = data;
e.crc = crc32_buf(data);
e.off = static_cast<quint32>(f.pos());
QByteArray c = deflateRaw(data);
if (c.size() < data.size()) { e.comp = c; e.meth = 8; }
else { e.comp = data; e.meth = 0; }
QByteArray nb = name.toUtf8();
QByteArray lh;
u32(lh,0x04034b50); u16(lh,20); u16(lh,0x0800); u16(lh,e.meth);
u16(lh,0); u16(lh,0);
u32(lh,e.crc);
u32(lh,static_cast<quint32>(e.comp.size()));
u32(lh,static_cast<quint32>(e.orig.size()));
u16(lh,static_cast<quint16>(nb.size())); u16(lh,0);
lh += nb;
f.write(lh); f.write(e.comp);
entries << e;
}
void close() {
quint32 cdOff = static_cast<quint32>(f.pos());
for (auto& e : entries) {
QByteArray nb = e.name.toUtf8(), cd;
u32(cd,0x02014b50); u16(cd,20); u16(cd,20); u16(cd,0x0800);
u16(cd,e.meth); u16(cd,0); u16(cd,0);
u32(cd,e.crc);
u32(cd,static_cast<quint32>(e.comp.size()));
u32(cd,static_cast<quint32>(e.orig.size()));
u16(cd,static_cast<quint16>(nb.size()));
u16(cd,0); u16(cd,0); u16(cd,0); u16(cd,0); u32(cd,0); u32(cd,e.off);
cd += nb; f.write(cd);
}
quint32 cdSz = static_cast<quint32>(f.pos()) - cdOff;
QByteArray eocd;
u32(eocd,0x06054b50); u16(eocd,0); u16(eocd,0);
u16(eocd,static_cast<quint16>(entries.size()));
u16(eocd,static_cast<quint16>(entries.size()));
u32(eocd,cdSz); u32(eocd,cdOff); u16(eocd,0);
f.write(eocd); f.close();
}
private:
QFile f; QList<ZEntry> entries;
};
} // namespace
// ============================================================
DocxBuilder::DocxBuilder() = default;
void DocxBuilder::clear() {
m_elements.clear();
m_images.clear();
m_imageCounter = 0;
}
// ============================================================
// Добавление элементов
// ============================================================
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.type = (level==1) ? DocElementType::Heading1
: (level==2) ? DocElementType::Heading2
: DocElementType::Heading3;
el.style.font = "Times New Roman";
el.style.bold = true;
el.style.fontSize = (level==1) ? 36 : (level==2) ? 30 : 26;
@ -42,318 +120,192 @@ void DocxBuilder::addHeading(const QString& text, int level) {
}
void DocxBuilder::addParagraph(const QString& text, const DocStyle& style) {
DocElement el;
el.type = DocElementType::NormalText;
el.text = text;
el.style = 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;
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;
}
if (!fi.exists()) { qWarning() << "Image not found:" << 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);
ImageEntry e;
e.sourcePath = imagePath; e.ext = fi.suffix().toLower();
e.rId = QString("rId%1").arg(10+m_imageCounter); e.index = m_imageCounter;
m_images.append(e);
DocElement el;
el.type = DocElementType::Image;
el.imagePath = imagePath;
el.imageWidthEmu = widthEmu;
el.imageHeightEmu = heightEmu;
// сохраняем индекс в fontSize (хак для передачи индекса)
DocElement el; el.type = DocElementType::Image; el.imagePath = imagePath;
el.imageWidthEmu = widthEmu; el.imageHeightEmu = heightEmu;
el.style.fontSize = m_imageCounter;
m_elements.append(el);
}
void DocxBuilder::addPageBreak() {
DocElement el;
el.type = DocElementType::NormalText;
el.text = "\f"; // маркер разрыва страницы
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;
}
ZipWriter zip;
if (!zip.open(filePath)) { qWarning() << "Cannot open:" << filePath; return false; }
QZipWriter zip(&file);
zip.setCompressionPolicy(QZipWriter::AutoCompress);
zip.add("[Content_Types].xml", buildContentTypesXml().toUtf8());
zip.add("_rels/.rels", buildRelsXml().toUtf8());
zip.add("word/document.xml", buildDocumentXml().toUtf8());
zip.add("word/_rels/document.xml.rels", buildDocumentRelsXml().toUtf8());
zip.add("word/styles.xml", buildStylesXml().toUtf8());
zip.add("word/settings.xml", buildSettingsXml().toUtf8());
// --- Обязательные части 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);
QFile f(img.sourcePath);
if (f.open(QIODevice::ReadOnly))
zip.add(QString("word/media/image%1.%2").arg(img.index).arg(img.ext), f.readAll());
}
}
zip.close();
file.close();
return true;
}
// ============================================================
// Построение XML документа
// XML helpers
// ============================================================
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;
}
}
QString DocxBuilder::escapeXml(const QString& t) {
QString o=t; o.replace("&","&amp;"); o.replace("<","&lt;"); o.replace(">","&gt;"); return o;
}
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);
QString DocxBuilder::makeRunProps(const DocStyle& s) {
return QString("<w:rPr><w:rFonts w:ascii=\"%1\" w:hAnsi=\"%1\"/>"
"<w:sz w:val=\"%2\"/><w:szCs w:val=\"%2\"/>%3%4</w:rPr>")
.arg(s.font).arg(s.fontSize)
.arg(s.bold ? "<w:b/><w:bCs/>" : "")
.arg(s.italic ? "<w:i/><w:iCs/>" : "");
}
// ============================================================
// Вспомогательные генераторы XML
// ============================================================
QString DocxBuilder::escapeXml(const QString& text) {
QString out = text;
out.replace("&", "&amp;");
out.replace("<", "&lt;");
out.replace(">", "&gt;");
out.replace("\"", "&quot;");
out.replace("'", "&apos;");
return out;
QString DocxBuilder::makeParaProps(const DocStyle& s, const QString& styleId) {
QString p = "<w:pPr>";
if (!styleId.isEmpty()) p += QString("<w:pStyle w:val=\"%1\"/>").arg(styleId);
if (s.center) p += "<w:jc w:val=\"center\"/>";
if (styleId.isEmpty()) p += "<w:spacing w:line=\"276\" w:lineRule=\"auto\"/>";
return p + "</w:pPr>";
}
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::makeParagraph(const QString& text, const DocStyle& s, const QString& styleId) {
QString r;
for (const QString& line : text.split('\n'))
r += "<w:p>" + makeParaProps(s, styleId) + "<w:r>" + makeRunProps(s)
+ "<w:t xml:space=\"preserve\">" + escapeXml(line) + "</w:t></w:r></w:p>";
return r;
}
QString DocxBuilder::makeTable(const TableData& table) {
// Считаем количество колонок
int cols = table.headers.isEmpty()
? (table.rows.isEmpty() ? 1 : table.rows[0].size())
: table.headers.size();
? (table.rows.isEmpty() ? 1 : table.rows[0].size()) : table.headers.size();
int cw = (cols>0) ? 9356/cols : 9356;
// Ширина колонки: таблица на всю ширину страницы 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>"
QString t = "<w:tbl><w:tblPr><w:tblStyle w:val=\"TableGrid\"/>"
"<w:tblW w:w=\"9356\" w:type=\"dxa\"/><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>";
"</w:tblBorders></w:tblPr><w:tblGrid>";
for (int c=0;c<cols;c++) t += QString("<w:gridCol w:w=\"%1\"/>").arg(cw);
t += "</w:tblGrid>";
// Сетка колонок
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>";
t += "<w:tr>";
for (const QString& h : table.headers) {
t += QString("<w:tc><w:tcPr><w:tcW w:w=\"%1\" w:type=\"dxa\"/>"
"<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"D9D9D9\"/></w:tcPr>"
"<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>"
"<w:t>%2</w:t></w:r></w:p></w:tc>").arg(cw).arg(escapeXml(h));
}
tbl += "</w:tr>";
t += "</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>";
t += "<w:tr>";
for (int c=0;c<cols;c++) {
QString ct = (c<row.size()) ? row[c] : "";
t += QString("<w:tc><w:tcPr><w:tcW w:w=\"%1\" w:type=\"dxa\"/></w:tcPr>"
"<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>"
"<w:t xml:space=\"preserve\">%2</w:t></w:r></w:p></w:tc>")
.arg(cw).arg(escapeXml(ct));
}
tbl += "</w:tr>";
t += "</w:tr>";
}
return t + "</w:tbl><w:p/>";
}
tbl += "</w:tbl>";
// Пустой параграф после таблицы (обязателен по спецификации OOXML)
tbl += "<w:p/>";
return tbl;
}
QString DocxBuilder::makeImageXml(int rId, int widthEmu, int heightEmu, int imgIdx) {
// EMU: 914400 = 1 дюйм
QString DocxBuilder::makeImageXml(int rId, int w, int h, int idx) {
return QString(
"<w:drawing>"
"<wp:inline distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\">"
"<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>"
"<wp:docPr id=\"%3\" name=\"Img%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);
"<pic:nvPicPr><pic:cNvPr id=\"%3\" name=\"Img%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(w).arg(h).arg(idx).arg(rId);
}
// ============================================================
// Системные XML-файлы DOCX
// ============================================================
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:
body += (el.text=="\f") ? QString(R"(<w:p><w:r><w:br w:type="page"/></w:r></w:p>)")
: makeParagraph(el.text, el.style);
break;
case DocElementType::Table: body += makeTable(el.table); break;
case DocElementType::Image:
for (const ImageEntry& img : m_images)
if (img.index == el.style.fontSize) {
body += "<w:p><w:r>" +
makeImageXml(img.rId.mid(3).toInt(),
el.imageWidthEmu, el.imageHeightEmu, img.index) +
"</w:r></w:p>";
break;
}
break;
}
}
return QString(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
"<w:document"
" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\""
" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\""
" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\""
" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\""
" xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
"<w:body>%1"
"<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);
}
QString DocxBuilder::buildContentTypesXml() {
QString ct =
@ -367,19 +319,18 @@ QString DocxBuilder::buildContentTypesXml() {
" 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);
QSet<QString> seen;
for (const ImageEntry& img : m_images)
if (!seen.contains(img.ext)) {
ct += QString("<Default Extension=\"%1\" ContentType=\"%2\"/>")
.arg(img.ext).arg(img.ext=="png" ? "image/png" : "image/jpeg");
seen.insert(img.ext);
}
ct += "</Types>";
return ct;
return ct + "</Types>";
}
QString DocxBuilder::buildRelsXml() {
return
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
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\""
@ -388,7 +339,7 @@ QString DocxBuilder::buildRelsXml() {
}
QString DocxBuilder::buildDocumentRelsXml() {
QString rels =
QString r =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
"<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
"<Relationship Id=\"rId1\""
@ -397,90 +348,60 @@ QString DocxBuilder::buildDocumentRelsXml() {
"<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\""
for (const ImageEntry& img : m_images)
r += 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;
" Target=\"media/image%2.%3\"/>")
.arg(img.rId).arg(img.index).arg(img.ext);
return r + "</Relationships>";
}
QString DocxBuilder::buildSettingsXml() {
return
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
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: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>"
"<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: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>"
"<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: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>"
"<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: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>"
"<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: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:tblBorders></w:tblPr></w:style>"
"</w:styles>";
}

View File

@ -7,10 +7,6 @@ CONFIG += c++17
TARGET = DocxWizard
TEMPLATE = app
# Подключаем приватные заголовки Qt для QZipWriter/QZipReader
# (входят в стандартную поставку Qt5)
QT += core-private
SOURCES += \
main.cpp \
DocxBuilder.cpp \
@ -20,18 +16,8 @@ HEADERS += \
DocxBuilder.h \
DocumentWizard.h
# Если QZipWriter недоступен как private используйте QuaZip:
# Установка: sudo apt install libquazip5-dev (Linux)
# или скачайте https://github.com/stachenov/quazip
#
# Тогда замените в DocxBuilder.cpp:
# #include <QtCore/private/qzipreader_p.h>
# #include <QtCore/private/qzipwriter_p.h>
# на:
# #include <quazip/quazip.h>
# #include <quazip/quazipfile.h>
# и адаптируйте метод save() под QuaZip API.
#
# Раскомментируйте, если используете QuaZip:
# LIBS += -lquazip5
# INCLUDEPATH += /usr/include/quazip5
# zlib входит в состав системы везде, где есть Qt
# Linux/macOS: подключается автоматически
# Windows (MSVC/MinGW): zlib поставляется вместе с Qt
unix: LIBS += -lz
win32: LIBS += -lzlib # при необходимости замените на -lz или укажите путь