#include "docxtopdf.h"
#include <QFile>
#include <QTextStream>
#include <QXmlStreamReader>
#include <QPrinter>
#include <QTextDocument>
#include <QTextCursor>
#include <QTextList>
#include <QDebug>
#include <quazip5/quazip.h>
#include <quazip5/quazipfile.h>
#include <QFileInfo>
#include <QImageReader>
#include <QPainter>
#include <QPdfWriter>
#include <QUuid>
// XML namespaces
const QString ns_w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
const QString ns_r1 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
const QString ns_r2 = "http://schemas.openxmlformats.org/package/2006/relationships";
const QString ns_wp = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
const QString ns_a = "http://schemas.openxmlformats.org/drawingml/2006/main";
const QString ns_pic = "http://schemas.openxmlformats.org/drawingml/2006/picture";
DocxToPdfConverter::DocxToPdfConverter(QObject *parent) : QObject(parent)
{
}
bool DocxToPdfConverter::convert(const QString &inputPath, const QString &outputPath)
{
// Reset state
m_paragraphs.clear();
m_styles.clear();
m_imageRelations.clear();
m_extractedImages.clear();
m_numberingFormats.clear();
m_pageSettings.clear();
m_currentPageSettingIndex = 0;
m_lastError.clear();
// Step 1: Extract and parse all necessary files from DOCX
if (!extractContentFromDocx(inputPath)) {
emit errorOccurred(m_lastError);
return false;
}
// Step 2: Render to PDF
if (!renderToPdf(outputPath)) {
emit errorOccurred(m_lastError);
return false;
}
emit conversionFinished(true);
return true;
}
QString DocxToPdfConverter::lastError() const
{
return m_lastError;
}
bool DocxToPdfConverter::extractContentFromDocx(const QString &docxPath)
{
QuaZip zip(docxPath);
if (!zip.open(QuaZip::mdUnzip)) {
m_lastError = "Failed to open DOCX file as ZIP archive";
return false;
}
// Parse relationships first to find all images
if (zip.setCurrentFile("word/_rels/document.xml.rels")) {
QuaZipFile relsFile(&zip);
if (relsFile.open(QIODevice::ReadOnly)) {
parseRelationships(relsFile.readAll());
relsFile.close();
}
}
// Parse styles
if (zip.setCurrentFile("word/styles.xml")) {
QuaZipFile stylesFile(&zip);
if (stylesFile.open(QIODevice::ReadOnly)) {
parseStylesXml(stylesFile.readAll());
stylesFile.close();
}
}
// Parse numbering (lists)
if (zip.setCurrentFile("word/numbering.xml")) {
QuaZipFile numberingFile(&zip);
if (numberingFile.open(QIODevice::ReadOnly)) {
parseNumberingXml(numberingFile.readAll());
numberingFile.close();
}
}
// Parse main document content
if (!zip.setCurrentFile("word/document.xml")) {
m_lastError = "Could not find document.xml in DOCX archive";
zip.close();
return false;
}
QuaZipFile documentFile(&zip);
if (!documentFile.open(QIODevice::ReadOnly)) {
m_lastError = "Could not open document.xml in DOCX archive";
zip.close();
return false;
}
bool extractResult = extractImages(docxPath); // 提前执行
if (!extractResult) {
zip.close();
return false;
}
bool result = parseDocumentXml(documentFile.readAll());
documentFile.close();
// // Extract images
// if (result) {
// result = extractImages(docxPath);
// }
zip.close();
return result;
}
bool DocxToPdfConverter::parseDocumentXml(const QByteArray &xmlData)
{
QXmlStreamReader xml(xmlData);
Paragraph currentPara;
TextRun currentRun;
bool inParagraph = false;
bool inRun = false;
bool inDrawing = false;
bool inSectionProperties = false;
QString currentImageId;
// Add default page settings (A4 with 1 inch margins)
PageSettings defaultPage;
defaultPage.size = QPageSize(QPageSize::A4).size(QPageSize::Point);
defaultPage.margins = QMarginsF(72, 72, 72, 72); // 1 inch margins
m_pageSettings.append(defaultPage);
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.namespaceUri() == ns_w) {
if (xml.name() == "p") {
inParagraph = true;
currentPara = Paragraph();
}
else if (inParagraph && xml.name() == "r") {
inRun = true;
currentRun = TextRun();
}
else if (inRun && xml.name() == "rPr") {
// Run properties (formatting)
while (xml.readNextStartElement()) {
if (xml.namespaceUri() != ns_w) {
xml.skipCurrentElement();
continue;
}
if (xml.name() == "sz") {
int halfPoints = getXmlAttribute(xml.attributes(), "val", ns_w).toInt();
// 确保最小字号为6pt,最大为72pt
qreal fontSize = qBound(6.0, halfPoints / 2.0, 72.0);
currentRun.charFormat.setFontPointSize(fontSize);
xml.skipCurrentElement();
}
else if (xml.name() == "color") {
QString color = getXmlAttribute(xml.attributes(), "val", ns_w);
if (!color.isEmpty()) {
//currentRun.charFormat.setForeground(QColor("#" + color));
if (color == "auto") {
currentRun.charFormat.setForeground(Qt::black); // 默认黑色
} else {
// 支持RGB颜色和主题颜色
QColor qcolor;
if (color.startsWith("FF")) { // 处理ABGR格式
color = color.mid(2);
qcolor = QColor("#" + color.mid(4,2) + color.mid(2,2) + color.left(2));
} else {
qcolor = QColor("#" + color);
}
if (qcolor.isValid()) {
currentRun.charFormat.setForeground(qcolor);
}
}
}
xml.skipCurrentElement();
}
else if (xml.name() == "b") {
currentRun.charFormat.setFontWeight(QFont::Bold);
xml.skipCurrentElement();
}
else if (xml.name() == "i") {
currentRun.charFormat.setFontItalic(true);
xml.skipCurrentElement();
}
else if (xml.name() == "u") {
QString underline = getXmlAttribute(xml.attributes(), "val", ns_w);
if (underline != "none") {
currentRun.charFormat.setFontUnderline(true);
}
xml.skipCurrentElement();
}
else if (xml.name() == "rFonts") {
QString font = getXmlAttribute(xml.attributes(), "ascii", ns_w);
if (!font.isEmpty()) {
currentRun.charFormat.setFontFamily(font);
}
xml.skipCurrentElement();
}
else {
xml.skipCurrentElement();
}
}
}
else if (inRun && xml.name() == "t") {
// Text content
currentRun.text = xml.readElementText();
currentPara.runs.append(currentRun);
}
else if (inRun && xml.name() == "drawing") {
// Start of drawing (image)
inDrawing = true;
currentImageId.clear(); // 确保每次处理新图片时清空ID
}
else if (inParagraph && xml.name() == "pPr") {
// Paragraph properties
while (xml.readNextStartElement()) {
if (xml.namespaceUri() != ns_w) {
xml.skipCurrentElement();
continue;
}
if (xml.name() == "pStyle") {
QString styleId = getXmlAttribute(xml.attributes(), "val", ns_w);
currentPara.styleName = styleId;
if (m_styles.contains(styleId)) {
currentPara.charFormat = m_styles[styleId];
}
xml.skipCurrentElement();
}
else if (xml.name() == "numPr") {
currentPara.isList = true;
xml.skipCurrentElement();
}
else if (xml.name() == "pageBreakBefore") {
currentPara.isPageBreak = true;
xml.skipCurrentElement();
}
else if (xml.name() == "spacing") {
QString before = getXmlAttribute(xml.attributes(), "before", ns_w);
QString after = getXmlAttribute(xml.attributes(), "after", ns_w);
QString line = getXmlAttribute(xml.attributes(), "line", ns_w);
QString lineRule = getXmlAttribute(xml.attributes(), "lineRule", ns_w);
if (!before.isEmpty()) {
currentPara.spacingBefore = twipsToPoints(before.toInt());
currentPara.blockFormat.setTopMargin(currentPara.spacingBefore);
}
if (!after.isEmpty()) {
currentPara.spacingAfter = twipsToPoints(after.toInt());
currentPara.blockFormat.setBottomMargin(currentPara.spacingAfter);
}
if (!line.isEmpty()) {
if (lineRule == "auto" || lineRule.isEmpty()) {
// 自动行距
currentPara.lineHeight = line.toInt() / 240.0; // 240 = 12pt * 20twips/pt
currentPara.blockFormat.setLineHeight(currentPara.lineHeight * 100,
QTextBlockFormat::ProportionalHeight);
} else if (lineRule == "exact") {
// 固定行距
currentPara.lineHeight = twipsToPoints(line.toInt());
currentPara.blockFormat.setLineHeight(currentPara.lineHeight,
QTextBlockFormat::FixedHeight);
} else if (lineRule == "atLeast") {
// 最小行距
currentPara.lineHeight = twipsToPoints(line.toInt());
currentPara.blockFormat.setLineHeight(currentPara.lineHeight,
QTextBlockFormat::MinimumHeight);
}
}
xml.skipCurrentElement();
}
else if (xml.name() == "ind") {
QString left = getXmlAttribute(xml.attributes(), "left", ns_w);
QString right = getXmlAttribute(xml.attributes(), "right", ns_w);
QString firstLine = getXmlAttribute(xml.attributes(), "firstLine", ns_w);
if (!left.isEmpty()) currentPara.blockFormat.setLeftMargin(twipsToPoints(left.toInt()));
if (!right.isEmpty()) currentPara.blockFormat.setRightMargin(twipsToPoints(right.toInt()));
if (!firstLine.isEmpty()) {
currentPara.blockFormat.setTextIndent(twipsToPoints(firstLine.toInt()));
}
xml.skipCurrentElement();
}
else if (xml.name() == "jc") {
QString align = getXmlAttribute(xml.attributes(), "val", ns_w);
if (align == "center") {
currentPara.blockFormat.setAlignment(Qt::AlignCenter);
}
else if (align == "right") {
currentPara.blockFormat.setAlignment(Qt::AlignRight);
}
else if (align == "both") {
currentPara.blockFormat.setAlignment(Qt::AlignJustify);
}
xml.skipCurrentElement();
}
else if (xml.name() == "sectPr") {
// Section properties (page settings)
inSectionProperties = true;
PageSettings pageSettings = m_pageSettings.last(); // Copy previous settings
while (xml.readNextStartElement()) {
if (xml.namespaceUri() != ns_w) {
xml.skipCurrentElement();
continue;
}
if (xml.name() == "pgSz") {
// Page size
QString width = getXmlAttribute(xml.attributes(), "w", ns_w);
QString height = getXmlAttribute(xml.attributes(), "h", ns_w);
if (!width.isEmpty() && !height.isEmpty()) {
pageSettings.size = QSizeF(twipsToPoints(width.toInt()),
twipsToPoints(height.toInt()));
}
xml.skipCurrentElement();
}
else if (xml.name() == "pgMar") {
// Page margins
QString top = getXmlAttribute(xml.attributes(), "top", ns_w);
QString right = getXmlAttribute(xml.attributes(), "right", ns_w);
QString bottom = getXmlAttribute(xml.attributes(), "bottom", ns_w);
QString left = getXmlAttribute(xml.attributes(), "left", ns_w);
if (!top.isEmpty()) pageSettings.margins.setTop(twipsToPoints(top.toInt()));
if (!right.isEmpty()) pageSettings.margins.setRight(twipsToPoints(right.toInt()));
if (!bottom.isEmpty()) pageSettings.margins.setBottom(twipsToPoints(bottom.toInt()));
if (!left.isEmpty()) pageSettings.margins.setLeft(twipsToPoints(left.toInt()));
xml.skipCurrentElement();
}
else if (xml.name() == "type") {
// Section break type
QString type = getXmlAttribute(xml.attributes(), "val", ns_w);
if (type == "nextPage") {
currentPara.isSectionBreak = true;
}
xml.skipCurrentElement();
}
else {
xml.skipCurrentElement();
}
}
m_pageSettings.append(pageSettings);
inSectionProperties = false;
}
else {
xml.skipCurrentElement();
}
}
}
}
else if (xml.namespaceUri() == ns_a && xml.name() == "blip") {
currentImageId = getXmlAttribute(xml.attributes(), "embed", ns_r2);
if (!currentImageId.isEmpty()) {
currentRun = TextRun(); // 重置运行
currentRun.isImage = true;
currentRun.imageName = QString("image_%1").arg(currentImageId);
}
xml.skipCurrentElement();
}
// else if (inDrawing && xml.namespaceUri() == ns_a && xml.name() == "blip") {
// // Image reference
// currentImageId = getXmlAttribute(xml.attributes(), "embed", ns_r2);
// xml.skipCurrentElement();
// }
else if (inDrawing && xml.namespaceUri() == ns_wp && xml.name() == "inline") {
qreal widthEmu = 0, heightEmu = 0;
while (xml.readNextStartElement()) {
if (xml.namespaceUri() == ns_wp && xml.name() == "extent") {
widthEmu = getXmlAttribute(xml.attributes(), "cx", ns_wp).toDouble();
heightEmu = getXmlAttribute(xml.attributes(), "cy", ns_wp).toDouble();
xml.skipCurrentElement();
} else {
xml.skipCurrentElement();
}
}
// EMU 转磅(1英寸=914400 EMU=72磅)
if (widthEmu > 0 && heightEmu > 0) {
currentRun.imageSize = QSizeF(widthEmu / 12700, heightEmu / 12700);
qDebug() << "Parsed image size from EMU:" << currentRun.imageSize << "pt";
}
}
}
else if (xml.isEndElement()) {
if (xml.namespaceUri() == ns_w) {
if (xml.name() == "p" && inParagraph) {
// 如果有图片ID但未处理,尝试绑定图片
if (!currentImageId.isEmpty() && m_extractedImages.contains(currentImageId)) {
currentRun.isImage = true;
currentRun.image = m_extractedImages[currentImageId];
currentPara.runs.append(currentRun);
}
// 保存段落
if (!currentPara.runs.isEmpty() || currentPara.isPageBreak) {
m_paragraphs.append(currentPara);
}
inParagraph = false;
}
// if (xml.name() == "p" && inParagraph) {
// // End of paragraph
// if (!currentPara.runs.isEmpty() || currentPara.isPageBreak || currentPara.isSectionBreak) {
// m_paragraphs.append(currentPara);
// }
// inParagraph = false;
// }
else if (xml.name() == "r" && inRun) {
if (currentRun.isImage && !currentImageId.isEmpty() && m_extractedImages.contains(currentImageId)) {
currentRun.image = m_extractedImages[currentImageId];
currentPara.runs.append(currentRun);
qDebug() << "Added image run:" << currentImageId
<< "Size:" << currentRun.image.size();
}
inRun = false;
}
}
else if (xml.namespaceUri() == ns_w && xml.name() == "drawing") {
// End of drawing (image)
inDrawing = false;
if (!currentImageId.isEmpty() && m_extractedImages.contains(currentImageId)) {
currentRun.isImage = true;
currentRun.image = m_extractedImages[currentImageId];
currentRun.imageName = QString("image_%1").arg(currentImageId);
currentRun.imageSize = QSizeF(
currentRun.image.width() / currentRun.image.dotsPerMeterX() * 72.0,
currentRun.image.height() / currentRun.image.dotsPerMeterY() * 72.0
);
currentPara.runs.append(currentRun);
}
currentImageId.clear();
}
}
}
if (xml.hasError()) {
m_lastError = QString("XML parsing error: %1").arg(xml.errorString());
return false;
}
qDebug() << "=== Image Debug Info ===";
qDebug() << "Extracted images count:" << m_extractedImages.size();
qDebug() << "Image relationships:" << m_imageRelations;
qDebug() << "Total paragraphs:" << m_paragraphs.size();
for (int i = 0; i < m_paragraphs.size(); ++i) {
const Paragraph& para = m_paragraphs[i];
qDebug() << "Paragraph" << i << "runs:" << para.runs.size();
for (int j = 0; j < para.runs.size(); ++j) {
if (para.runs[j].isImage) {
qDebug() << " Run" << j << "is IMAGE, ID:" << para.runs[j].imageName;
}
}
}
return true;
}
bool DocxToPdfConverter::parseStylesXml(const QByteArray &xmlData)
{
QXmlStreamReader xml(xmlData);
QString currentStyleId;
QTextCharFormat currentFormat;
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if (xml.isStartElement() && xml.namespaceUri() == ns_w) {
if (xml.name() == "style") {
currentStyleId = getXmlAttribute(xml.attributes(), "styleId", ns_w);
QString type = getXmlAttribute(xml.attributes(), "type", ns_w);
if (type != "character" && type != "paragraph") {
currentStyleId.clear();
}
}
else if (!currentStyleId.isEmpty() && xml.name() == "rPr") {
while (xml.readNextStartElement()) {
if (xml.namespaceUri() != ns_w) {
xml.skipCurrentElement();
continue;
}
if (xml.name() == "sz") {
int halfPoints = getXmlAttribute(xml.attributes(), "val", ns_w).toInt();
currentFormat.setFontPointSize(halfPoints / 2.0);
xml.skipCurrentElement();
}
else if (xml.name() == "color") {
QString color = getXmlAttribute(xml.attributes(), "val", ns_w);
if (!color.isEmpty()) {
currentFormat.setForeground(QColor("#" + color));
}
xml.skipCurrentElement();
}
else if (xml.name() == "b") {
currentFormat.setFontWeight(QFont::Bold);
xml.skipCurrentElement();
}
else if (xml.name() == "i") {
currentFormat.setFontItalic(true);
xml.skipCurrentElement();
}
else if (xml.name() == "rFonts") {
QString font = getXmlAttribute(xml.attributes(), "ascii", ns_w);
if (!font.isEmpty()) {
currentFormat.setFontFamily(font);
}
xml.skipCurrentElement();
}
else {
xml.skipCurrentElement();
}
}
}
}
else if (xml.isEndElement() && xml.namespaceUri() == ns_w) {
if (xml.name() == "style" && !currentStyleId.isEmpty()) {
m_styles[currentStyleId] = currentFormat;
currentStyleId.clear();
currentFormat = QTextCharFormat();
}
}
}
if (xml.hasError()) {
m_lastError = QString("Styles XML parsing error: %1").arg(xml.errorString());
return false;
}
return true;
}
bool DocxToPdfConverter::parseNumberingXml(const QByteArray &xmlData)
{
QXmlStreamReader xml(xmlData);
int currentNumId = -1;
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if (xml.isStartElement() && xml.namespaceUri() == ns_w) {
if (xml.name() == "num") {
currentNumId = getXmlAttribute(xml.attributes(), "numId", ns_w).toInt();
}
else if (currentNumId != -1 && xml.name() == "numFmt") {
QString format = getXmlAttribute(xml.attributes(), "val", ns_w);
m_numberingFormats[currentNumId] = format;
}
}
else if (xml.isEndElement() && xml.namespaceUri() == ns_w) {
if (xml.name() == "num") {
currentNumId = -1;
}
}
}
if (xml.hasError()) {
m_lastError = QString("Numbering XML parsing error: %1").arg(xml.errorString());
return false;
}
return true;
}
bool DocxToPdfConverter::parseRelationships(const QByteArray &xmlData)
{
QXmlStreamReader xml(xmlData);
const QString relNs = "http://schemas.openxmlformats.org/package/2006/relationships";
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement() && xml.name() == "Relationship") {
// 直接获取属性值(不依赖命名空间)
QXmlStreamAttributes attrs = xml.attributes();
QString id = attrs.value("Id").toString();
QString target = attrs.value("Target").toString();
QString type = attrs.value("Type").toString();
// 检查是否是图片类型
if (type.contains("image") ||
type.endsWith("png", Qt::CaseInsensitive) ||
type.endsWith("jpg", Qt::CaseInsensitive) ||
type.endsWith("jpeg", Qt::CaseInsensitive)) {
// 标准化路径
QString fullPath = "word/" + target;
fullPath = fullPath.replace("\\", "/");
m_imageRelations[id] = fullPath;
qDebug() << "Found image relationship: ID =" << id
<< "Path =" << fullPath
<< "Type =" << type;
}
}
}
if (xml.hasError()) {
m_lastError = "Relationships XML error: " + xml.errorString();
return false;
}
return true;
}
bool DocxToPdfConverter::extractImages(const QString &docxPath)
{
QuaZip zip(docxPath);
if (!zip.open(QuaZip::mdUnzip)) {
m_lastError = "Failed to open DOCX file";
return false;
}
for (const auto &rel : m_imageRelations.keys()) {
QString imagePath = m_imageRelations[rel];
if (!zip.setCurrentFile(imagePath)) {
qDebug() << "Image not found:" << imagePath;
continue;
}
QuaZipFile imageFile(&zip);
if (!imageFile.open(QIODevice::ReadOnly)) {
qDebug() << "Failed to open image:" << imagePath;
continue;
}
QByteArray imageData = imageFile.readAll();
imageFile.close();
QImage image;
if (image.loadFromData(imageData)) {
// // 转换为不透明格式
// if (image.format() == QImage::Format_ARGB32) {
// image = image.convertToFormat(QImage::Format_RGB32);
// }
// m_extractedImages[rel] = image;
qDebug() << "Successfully loaded image:" << imagePath
<< "Size:" << image.size()
<< "Format:" << image.format();
// 确保图片有有效的DPI信息
if (image.dotsPerMeterX() <= 0 || image.dotsPerMeterY() <= 0) {
image.setDotsPerMeterX(2835); // 72 DPI
image.setDotsPerMeterY(2835); // 72 DPI
}
m_extractedImages[rel] = image;
} else {
qDebug() << "Failed to load image data:" << imagePath
<< "Data size:" << imageData.size();
// 创建占位图
QImage placeholder(300, 100, QImage::Format_RGB32);
placeholder.fill(Qt::white);
QPainter painter(&placeholder);
painter.setPen(Qt::red);
painter.drawText(10, 50, "Image Load Error");
painter.end();
m_extractedImages[rel] = placeholder;
}
}
qDebug() << "Extracted images:" << m_extractedImages.keys();
zip.close();
return true;
}
bool DocxToPdfConverter::renderToPdf(const QString &outputPath)
{
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(outputPath);
applyCurrentPageSettings(printer);
QTextDocument document;
document.setPageSize(printer.pageRect(QPrinter::Point).size());
document.setDocumentMargin(0); // 使用边距控制
// 设置默认字体和文本选项
QFont defaultFont("Times New Roman", 12);
document.setDefaultFont(defaultFont);
QTextOption textOption;
textOption.setAlignment(Qt::AlignLeft);
textOption.setWrapMode(QTextOption::WordWrap);
document.setDefaultTextOption(textOption);
QTextCursor cursor(&document);
for (const Paragraph ¶ : m_paragraphs) {
// 处理分页和分段
if (para.isSectionBreak) {
m_currentPageSettingIndex = qMin(m_currentPageSettingIndex + 1, m_pageSettings.size() - 1);
applyCurrentPageSettings(printer);
document.setPageSize(printer.pageRect(QPrinter::Point).size());
QTextBlockFormat breakFormat;
breakFormat.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
cursor.insertBlock(breakFormat);
continue;
}
if (para.isPageBreak) {
QTextBlockFormat breakFormat;
breakFormat.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
cursor.insertBlock(breakFormat);
continue;
}
// 设置段落格式
QTextBlockFormat blockFormat = para.blockFormat;
// 处理列表
if (para.isList) {
QTextListFormat listFormat;
listFormat.setIndent(para.listLevel + 1);
listFormat.setStyle(QTextListFormat::ListDecimal);
cursor.insertList(listFormat);
} else {
cursor.insertBlock(blockFormat);
}
// 添加文本运行
for (const TextRun &run : para.runs) {
qDebug() << "run.isImage:" << run.isImage;
if (run.isImage && !run.image.isNull()) {
qDebug() << "存在图片";
// 1. 确保图片格式兼容PDF(ARGB32可能有问题,转换为RGB32)
QImage compatibleImage = run.image;
if (compatibleImage.format() == QImage::Format_ARGB32) {
compatibleImage = compatibleImage.convertToFormat(QImage::Format_RGB32);
}
// 2. 直接使用内存资源(不依赖文件路径)
QString resourceName = "img_" + QUuid::createUuid().toString(QUuid::WithoutBraces);
document.addResource(QTextDocument::ImageResource, QUrl(resourceName), compatibleImage);
// 3. 计算并设置图片尺寸(单位:磅)
qreal widthPt = run.imageSize.width() > 0 ? run.imageSize.width() : 200; // 默认200磅
qreal heightPt = run.imageSize.height() > 0 ? run.imageSize.height() :
widthPt * compatibleImage.height() / compatibleImage.width();
// 4. 限制图片不超过页面宽度
qreal maxWidth;
if (m_currentPageSettingIndex < m_pageSettings.size()) {
// 使用自定义的页面设置
const PageSettings &settings = m_pageSettings[m_currentPageSettingIndex];
maxWidth = printer.pageRect(QPrinter::Point).width() - settings.margins.left() - settings.margins.right();
} else {
// 回退到打印机默认设置(单位:磅)
QPageLayout layout = printer.pageLayout();
maxWidth = printer.pageRect(QPrinter::Point).width() - layout.margins().left() - layout.margins().right();
}
if (widthPt > maxWidth) {
heightPt = heightPt * (maxWidth / widthPt);
widthPt = maxWidth;
}
// 5. 插入图片
QTextImageFormat imageFormat;
imageFormat.setName(resourceName);
imageFormat.setWidth(widthPt);
imageFormat.setHeight(heightPt);
cursor.insertImage(imageFormat);
qDebug() << "Inserted image:" << resourceName
<< "Size (pt):" << widthPt << "x" << heightPt
<< "Original size:" << compatibleImage.size();
qDebug() << "Document image resources:" << document.resource(QTextDocument::ImageResource, QUrl()).isValid();
// 强制立即渲染(测试用)
document.print(&printer);
if (printer.printerState() == QPrinter::Error) {
qDebug() << "PDF generation failed:";
} else {
qDebug() << "PDF generated successfully at" << outputPath;
}
}
else {
// 合并格式并确保字体属性正确
QTextCharFormat format = para.charFormat;
format.merge(run.charFormat);
// 确保重要属性不被覆盖
if (run.charFormat.hasProperty(QTextFormat::FontFamily)) {
format.setFontFamily(run.charFormat.fontFamily());
}
if (run.charFormat.hasProperty(QTextFormat::FontPointSize)) {
format.setFontPointSize(run.charFormat.fontPointSize());
}
if (run.charFormat.hasProperty(QTextFormat::FontWeight)) {
format.setFontWeight(run.charFormat.fontWeight());
}
if (run.charFormat.hasProperty(QTextFormat::FontItalic)) {
format.setFontItalic(run.charFormat.fontItalic());
}
if (run.charFormat.hasProperty(QTextFormat::TextUnderlineStyle)) {
format.setFontUnderline(run.charFormat.fontUnderline());
}
if (run.charFormat.hasProperty(QTextFormat::ForegroundBrush)) {
format.setForeground(run.charFormat.foreground());
}
cursor.insertText(run.text, format);
}
}
}
// 渲染PDF
document.print(&printer);
if (printer.printerState() == QPrinter::Error) {
//m_lastError = "Failed to generate PDF: " + printer.errorString();
return false;
}
return true;
}
QString DocxToPdfConverter::getXmlAttribute(const QXmlStreamAttributes &attrs, const QString &name, const QString &ns)
{
// 首先尝试使用指定命名空间
if (!ns.isEmpty()) {
QString value = attrs.value(ns, name).toString();
if (!value.isEmpty()) return value;
}
// 然后尝试不使用命名空间
return attrs.value(name).toString();
}
void DocxToPdfConverter::applyCurrentPageSettings(QPrinter &printer)
{
if (m_currentPageSettingIndex < m_pageSettings.size()) {
const PageSettings &settings = m_pageSettings[m_currentPageSettingIndex];
// Find closest QPageSize
QPageSize pageSize(settings.size, QPageSize::Point, "", QPageSize::ExactMatch);
if (!pageSize.isValid()) {
pageSize = QPageSize(settings.size, QPageSize::Point);
}
printer.setPageSize(pageSize);
printer.setPageMargins(settings.margins, QPageLayout::Point);
}
}
当前代码的打印存在如下问题:cursor, ========== cshape: 0
cursor, size from XGetDefault: 24
cursor, size from ptrXcursorLibraryGetDefaultSize: 24
Found image relationship: ID = "rId6" Path = "word/media/sign20250805.102526_photo.png" Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
Successfully loaded image: "word/media/sign20250805.102526_photo.png" Size: QSize(1232, 588) Format: QImage::Format_ARGB32
Extracted images: ("rId6")
=== Image Debug Info ===
Extracted images count: 1
Image relationships: QMap(("rId6", "word/media/sign20250805.102526_photo.png"))
Total paragraphs: 25
Paragraph 0 runs: 7
Paragraph 1 runs: 3
Paragraph 2 runs: 3
Paragraph 3 runs: 15
Paragraph 4 runs: 5
Paragraph 5 runs: 10
Paragraph 6 runs: 8
Paragraph 7 runs: 6
Paragraph 8 runs: 8
Paragraph 9 runs: 4
Paragraph 10 runs: 5
Paragraph 11 runs: 4
Paragraph 12 runs: 4
Paragraph 13 runs: 2
Paragraph 14 runs: 2
Paragraph 15 runs: 2
Paragraph 16 runs: 2
Paragraph 17 runs: 1
Paragraph 18 runs: 1
Paragraph 19 runs: 24
Paragraph 20 runs: 1
Paragraph 21 runs: 7
Paragraph 22 runs: 2
Paragraph 23 runs: 1
Paragraph 24 runs: 12
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false
run.isImage: false,还是依然转换之后不存在签名图片,如何解决
最新发布