CHARFORMAT


win32asm下是在windows.inc里

CHARFORMAT structure of MFC是Microsoft Foundation Classes (MFC)即 微软类库中 字符格式类的结构。结构中不可或缺的两个成员是dwMask和 dwEffects,前者是定义或设置字符的字体、颜色、粗体、斜体、下划线等属性值,后者则是相关属性的效果。

编辑本段基本语义

CHARFORMAT Structure
The CHARFORMAT structure contains information about character formatting in a rich edit control.
Rich Edit 2.0: The CHARFORMAT2 structure is a Microsoft Rich Edit 2.0 extension of the CHARFORMAT structure. Rich Edit 2.0 and later allows you to use either structure with the EM_GETCHARFORMAT and EM_SETCHARFORMAT messages.
Syntax
typedef struct _charformat {
UINT cbSize;
DWORD dwMask;
DWORD dwEffects;
LONG yHeight;
LONG yOffset;
COLORREF crTextColor;
BYTE bCharSet;
BYTE bPitchAndFamily;
TCHAR szFaceName[LF_FACESIZE];
} CHARFORMAT;
Members
cbSize
Size in bytes of the specified structure. This member must be set before passing the structure to the rich edit control.

编辑本段dwMask

Members containing valid information or attributes to set. This member can be zero, one, or more than one of the following values.
CFM_BOLD
The CFE_BOLD value of the dwEffects member is valid.
CFM_CHARSET
The bCharSet member is valid.
CFM_COLOR
The crTextColor member and the CFE_AUTOCOLOR value of the dwEffects member are valid.
CFM_FACE
The szFaceName member is valid.
CFM_ITALIC
The CFE_ITALIC value of the dwEffects member is valid.
CFM_OFFSET
The yOffset member is valid.
CFM_PROTECTED
The CFE_PROTECTED value of the dwEffects member is valid.
CFM_SIZE
The yHeight member is valid.
CFM_STRIKEOUT
The CFE_STRIKEOUT value of the dwEffects member is valid.
CFM_UNDERLINE.
The CFE_UNDERLINE value of the dwEffects member is valid.

编辑本段dwEffects

Character effects. This member can be a combination of the following values.
CFE_AUTOCOLOR
The text color is the return value of GetSysColor(COLOR_WINDOWTEXT).
CFE_BOLD
Characters are bold.
CFE_DISABLED
RichEdit 2.0 and later: Characters are displayed with a shadow that is offset by 3/4 point or one pixel, whichever is larger.
CFE_ITALIC
Characters are italic.
CFE_STRIKEOUT
Characters are struck.
CFE_UNDERLINE
Characters are underlined.
CFE_PROTECTED
Characters are protected; an attempt to modify them will cause an EN_PROTECTED notification message.
yHeight
Character height, in twips (1/1440 of an inch or 1/20 of a printer's point).
yOffset
Character offset, in twips, from the baseline. If the value of this member is positive, the character is a superscript; if it is negative, the character is a subscript.
crTextColor
Text color. This member is ignored if the CFE_AUTOCOLOR character effect is specified. To generate a COLORREF, use the RGB macro.
bCharSet
Character set value. The bCharSet member can be one of the values specified for the lfCharSet member of the LOGFONT structure. Rich Edit 3.0 may override this value if it is invalid for the target characters.
bPitchAndFamily
Font family and pitch. This member is the same as the lfPitchAndFamily member of the LOGFONT structure.
szFaceName
Null-terminated character array specifying the font name.

编辑本段Remarks

To turn off a formatting attribute, set the appropriate value in dwMask but do not set the corresponding value in dwEffects. For example, to turn off italics, set CFM_ITALIC but do not set CFE_ITALIC.
相信大家没心情看完,现在我只解释一下dwmask与dweffects的区别:dweffects记录了字体的部分信息,如粗体,但是这些信息是否有效却是由swmask决定的,请看下面的解释,如:CFM_BOLD The CFE_BOLD value of the dwEffects member is valid.也就是说只有当dwmask&cfm_bold!=0时才表明字体 加粗,这也是为什么在最后help里面会强调关闭字体作用时,设置dwmask的值,而不是dweffects。(个人愚见,错误之处见谅)
#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 &para : 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,还是依然转换之后不存在签名图片,如何解决
最新发布
08-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值